agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 1/4] Fix GiST and SP-GiST ordering by distance for NULLs and NaNs
8+ messages / 2 participants
[nested] [flat]

* [PATCH 1/4] Fix GiST and SP-GiST ordering by distance for NULLs and NaNs
@ 2019-09-10 14:26  Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Nikita Glukhov @ 2019-09-10 14:26 UTC (permalink / raw)

---
 src/backend/access/gist/gistget.c                 | 68 ++++++++---------------
 src/backend/access/gist/gistscan.c                | 18 +++---
 src/backend/access/index/indexam.c                | 22 ++++----
 src/backend/access/spgist/spgkdtreeproc.c         |  2 +-
 src/backend/access/spgist/spgproc.c               | 19 +++++--
 src/backend/access/spgist/spgquadtreeproc.c       |  2 +-
 src/backend/access/spgist/spgscan.c               | 60 ++++++++++++--------
 src/backend/utils/adt/geo_spgist.c                | 40 +++++++++----
 src/include/access/genam.h                        | 10 +++-
 src/include/access/gist_private.h                 | 27 ++-------
 src/include/access/spgist.h                       |  4 +-
 src/include/access/spgist_private.h               | 18 +++---
 src/test/regress/expected/create_index_spgist.out | 10 ++++
 src/test/regress/sql/create_index_spgist.sql      |  5 ++
 14 files changed, 167 insertions(+), 138 deletions(-)

diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index db633a9..22d790d 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -112,9 +112,8 @@ gistkillitems(IndexScanDesc scan)
  * Similarly, *recheck_distances_p is set to indicate whether the distances
  * need to be rechecked, and it is also ignored for non-leaf entries.
  *
- * If we are doing an ordered scan, so->distancesValues[] and
- * so->distancesNulls[] is filled with distance data from the distance()
- * functions before returning success.
+ * If we are doing an ordered scan, so->distances[] is filled with distance
+ * data from the distance() functions before returning success.
  *
  * We must decompress the key in the IndexTuple before passing it to the
  * sk_funcs (which actually are the opclass Consistent or Distance methods).
@@ -135,8 +134,7 @@ gistindex_keytest(IndexScanDesc scan,
 	GISTSTATE  *giststate = so->giststate;
 	ScanKey		key = scan->keyData;
 	int			keySize = scan->numberOfKeys;
-	double	   *distance_value_p;
-	bool	   *distance_null_p;
+	IndexOrderByDistance *distance_p;
 	Relation	r = scan->indexRelation;
 
 	*recheck_p = false;
@@ -155,8 +153,8 @@ gistindex_keytest(IndexScanDesc scan,
 			elog(ERROR, "invalid GiST tuple found on leaf page");
 		for (i = 0; i < scan->numberOfOrderBys; i++)
 		{
-			so->distanceValues[i] = -get_float8_infinity();
-			so->distanceNulls[i] = false;
+			so->distances[i].value = -get_float8_infinity();
+			so->distances[i].isnull = false;
 		}
 		return true;
 	}
@@ -240,8 +238,7 @@ gistindex_keytest(IndexScanDesc scan,
 
 	/* OK, it passes --- now let's compute the distances */
 	key = scan->orderByData;
-	distance_value_p = so->distanceValues;
-	distance_null_p = so->distanceNulls;
+	distance_p = so->distances;
 	keySize = scan->numberOfOrderBys;
 	while (keySize > 0)
 	{
@@ -256,8 +253,8 @@ gistindex_keytest(IndexScanDesc scan,
 		if ((key->sk_flags & SK_ISNULL) || isNull)
 		{
 			/* Assume distance computes as null */
-			*distance_value_p = 0.0;
-			*distance_null_p = true;
+			distance_p->value = 0.0;
+			distance_p->isnull = true;
 		}
 		else
 		{
@@ -294,13 +291,12 @@ gistindex_keytest(IndexScanDesc scan,
 									 ObjectIdGetDatum(key->sk_subtype),
 									 PointerGetDatum(&recheck));
 			*recheck_distances_p |= recheck;
-			*distance_value_p = DatumGetFloat8(dist);
-			*distance_null_p = false;
+			distance_p->value = DatumGetFloat8(dist);
+			distance_p->isnull = false;
 		}
 
 		key++;
-		distance_value_p++;
-		distance_null_p++;
+		distance_p++;
 		keySize--;
 	}
 
@@ -313,8 +309,7 @@ gistindex_keytest(IndexScanDesc scan,
  *
  * scan: index scan we are executing
  * pageItem: search queue item identifying an index page to scan
- * myDistanceValues: distances array associated with pageItem, or NULL at the root
- * myDistanceNulls: null flags for myDistanceValues array, or NULL at the root
+ * myDistances: distances array associated with pageItem, or NULL at the root
  * tbm: if not NULL, gistgetbitmap's output bitmap
  * ntids: if not NULL, gistgetbitmap's output tuple counter
  *
@@ -332,8 +327,7 @@ gistindex_keytest(IndexScanDesc scan,
  */
 static void
 gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
-			 double *myDistanceValues, bool *myDistanceNulls,
-			 TIDBitmap *tbm, int64 *ntids)
+			 IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
 {
 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
 	GISTSTATE  *giststate = so->giststate;
@@ -370,7 +364,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		GISTSearchItem *item;
 
 		/* This can't happen when starting at the root */
-		Assert(myDistanceValues != NULL && myDistanceNulls != NULL);
+		Assert(myDistances != NULL);
 
 		oldcxt = MemoryContextSwitchTo(so->queueCxt);
 
@@ -380,10 +374,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		item->data.parentlsn = pageItem->data.parentlsn;
 
 		/* Insert it into the queue using same distances as for this page */
-		memcpy(GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-			   myDistanceValues, sizeof(double) * scan->numberOfOrderBys);
-		memcpy(GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-			   myDistanceNulls, sizeof(bool) * scan->numberOfOrderBys);
+		memcpy(item->distances, myDistances,
+			   sizeof(item->distances[0]) * scan->numberOfOrderBys);
 
 		pairingheap_add(so->queue, &item->phNode);
 
@@ -527,10 +519,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 			}
 
 			/* Insert it into the queue using new distance data */
-			memcpy(GISTSearchItemDistanceValues(item, nOrderBys),
-				   so->distanceValues, sizeof(double) * nOrderBys);
-			memcpy(GISTSearchItemDistanceNulls(item, nOrderBys),
-				   so->distanceNulls, sizeof(bool) * nOrderBys);
+			memcpy(item->distances, so->distances,
+				   sizeof(item->distances[0]) * nOrderBys);
 
 			pairingheap_add(so->queue, &item->phNode);
 
@@ -595,8 +585,7 @@ getNextNearest(IndexScanDesc scan)
 			scan->xs_recheck = item->data.heap.recheck;
 
 			index_store_float8_orderby_distances(scan, so->orderByTypes,
-												 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-												 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
+												 item->distances,
 												 item->data.heap.recheckDistances);
 
 			/* in an index-only scan, also return the reconstructed tuple. */
@@ -609,10 +598,7 @@ getNextNearest(IndexScanDesc scan)
 			/* visit an index page, extract its items into queue */
 			CHECK_FOR_INTERRUPTS();
 
-			gistScanPage(scan, item,
-						 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-						 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-						 NULL, NULL);
+			gistScanPage(scan, item, item->distances, NULL, NULL);
 		}
 
 		pfree(item);
@@ -650,7 +636,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 
 		fakeItem.blkno = GIST_ROOT_BLKNO;
 		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-		gistScanPage(scan, &fakeItem, NULL, NULL, NULL, NULL);
+		gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
 	}
 
 	if (scan->numberOfOrderBys > 0)
@@ -744,10 +730,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * this page, we fall out of the inner "do" and loop around to
 				 * return them.
 				 */
-				gistScanPage(scan, item,
-							 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-							 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-							 NULL, NULL);
+				gistScanPage(scan, item, item->distances, NULL, NULL);
 
 				pfree(item);
 			} while (so->nPageData == 0);
@@ -778,7 +761,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 	fakeItem.blkno = GIST_ROOT_BLKNO;
 	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-	gistScanPage(scan, &fakeItem, NULL, NULL, tbm, &ntids);
+	gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
 
 	/*
 	 * While scanning a leaf page, ItemPointers of matching heap tuples will
@@ -793,10 +776,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 		CHECK_FOR_INTERRUPTS();
 
-		gistScanPage(scan, item,
-					 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-					 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-					 tbm, &ntids);
+		gistScanPage(scan, item, item->distances, tbm, &ntids);
 
 		pfree(item);
 	}
diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c
index e72bf08..323a19d 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -14,6 +14,8 @@
  */
 #include "postgres.h"
 
+#include <math.h>
+
 #include "access/gist_private.h"
 #include "access/gistscan.h"
 #include "access/relscan.h"
@@ -33,26 +35,23 @@ pairingheap_GISTSearchItem_cmp(const pairingheap_node *a, const pairingheap_node
 	const GISTSearchItem *sb = (const GISTSearchItem *) b;
 	IndexScanDesc scan = (IndexScanDesc) arg;
 	int			i;
-	double	   *da = GISTSearchItemDistanceValues(sa, scan->numberOfOrderBys),
-			   *db = GISTSearchItemDistanceValues(sb, scan->numberOfOrderBys);
-	bool	   *na = GISTSearchItemDistanceNulls(sa, scan->numberOfOrderBys),
-			   *nb = GISTSearchItemDistanceNulls(sb, scan->numberOfOrderBys);
 
 	/* Order according to distance comparison */
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (na[i])
+		if (sa->distances[i].isnull)
 		{
-			if (!nb[i])
+			if (!sb->distances[i].isnull)
 				return -1;
 		}
-		else if (nb[i])
+		else if (sb->distances[i].isnull)
 		{
 			return 1;
 		}
 		else
 		{
-			int			cmp = -float8_cmp_internal(da[i], db[i]);
+			int			cmp = -float8_cmp_internal(sa->distances[i].value,
+												   sb->distances[i].value);
 
 			if (cmp != 0)
 				return cmp;
@@ -100,8 +99,7 @@ gistbeginscan(Relation r, int nkeys, int norderbys)
 	so->queueCxt = giststate->scanCxt;	/* see gistrescan */
 
 	/* workspaces with size dependent on numberOfOrderBys: */
-	so->distanceValues = palloc(sizeof(double) * scan->numberOfOrderBys);
-	so->distanceNulls = palloc(sizeof(bool) * scan->numberOfOrderBys);
+	so->distances = palloc(sizeof(so->distances[0]) * scan->numberOfOrderBys);
 	so->qual_ok = true;			/* in case there are zero keys */
 	if (scan->numberOfOrderBys > 0)
 	{
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 2e8f53a..442f414 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -847,14 +847,14 @@ index_getprocinfo(Relation irel,
  */
 void
 index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
-									 double *distanceValues,
-									 bool *distanceNulls, bool recheckOrderBy)
+									 IndexOrderByDistance *distances,
+									 bool recheckOrderBy)
 {
 	int			i;
 
 	scan->xs_recheckorderby = recheckOrderBy;
 
-	if (!distanceValues)
+	if (!distances)
 	{
 		Assert(!scan->xs_recheckorderby);
 
@@ -869,11 +869,11 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (distanceNulls && distanceNulls[i])
-		{
+		scan->xs_orderbynulls[i] = distances[i].isnull;
+
+		if (scan->xs_orderbynulls[i])
 			scan->xs_orderbyvals[i] = (Datum) 0;
-			scan->xs_orderbynulls[i] = true;
-		}
+
 		if (orderByTypes[i] == FLOAT8OID)
 		{
 #ifndef USE_FLOAT8_BYVAL
@@ -881,8 +881,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float8GetDatum(distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float8GetDatum(distances[i].value);
 		}
 		else if (orderByTypes[i] == FLOAT4OID)
 		{
@@ -892,8 +892,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float4GetDatum((float4) distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float4GetDatum((float4) distances[i].value);
 		}
 		else
 		{
diff --git a/src/backend/access/spgist/spgkdtreeproc.c b/src/backend/access/spgist/spgkdtreeproc.c
index 9d479fe..66ab1ac 100644
--- a/src/backend/access/spgist/spgkdtreeproc.c
+++ b/src/backend/access/spgist/spgkdtreeproc.c
@@ -271,7 +271,7 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
 		BOX			infArea;
 		BOX		   *area;
 
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 
 		if (in->level == 0)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 688638a..1059456 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -59,20 +59,29 @@ point_box_distance(Point *point, BOX *box)
  * is expected to be point, non-leaf key is expected to be box.  Scan key
  * arguments are expected to be points.
  */
-double *
+IndexOrderByDistance *
 spg_key_orderbys_distances(Datum key, bool isLeaf,
 						   ScanKey orderbys, int norderbys)
 {
 	int			sk_num;
-	double	   *distances = (double *) palloc(norderbys * sizeof(double)),
+	IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * norderbys),
 			   *distance = distances;
 
 	for (sk_num = 0; sk_num < norderbys; ++sk_num, ++orderbys, ++distance)
 	{
-		Point	   *point = DatumGetPointP(orderbys->sk_argument);
+		if (orderbys->sk_flags & SK_ISNULL)
+		{
+			distance->isnull = true;
+			distance->value = 0;
+		}
+		else
+		{
+			Point	   *point = DatumGetPointP(orderbys->sk_argument);
 
-		*distance = isLeaf ? point_point_distance(point, DatumGetPointP(key))
-			: point_box_distance(point, DatumGetBoxP(key));
+			distance->isnull = false;
+			distance->value = isLeaf ? point_point_distance(point, DatumGetPointP(key))
+				: point_box_distance(point, DatumGetBoxP(key));
+		}
 	}
 
 	return distances;
diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index e50108e..3f59bea 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -247,7 +247,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 	 */
 	if (in->norderbys > 0)
 	{
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 
 		if (in->level == 0)
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2bd4037..a837789 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -28,7 +28,8 @@
 
 typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
 							   Datum leafValue, bool isNull, bool recheck,
-							   bool recheckDistances, double *distances);
+							   bool recheckDistances,
+							   IndexOrderByDistance *distances);
 
 /*
  * Pairing heap comparison function for the SpGistSearchItem queue.
@@ -58,14 +59,23 @@ pairingheap_SpGistSearchItem_cmp(const pairingheap_node *a,
 		/* Order according to distance comparison */
 		for (i = 0; i < so->numberOfOrderBys; i++)
 		{
-			if (isnan(sa->distances[i]) && isnan(sb->distances[i]))
+			if (sa->distances[i].isnull)
+			{
+				if (!sb->distances[i].isnull)
+					return -1;
+				continue;
+			}
+			else if (sb->distances[i].isnull)
+				return 1;
+
+			if (isnan(sa->distances[i].value) && isnan(sb->distances[i].value))
 				continue;		/* NaN == NaN */
-			if (isnan(sa->distances[i]))
+			if (isnan(sa->distances[i].value))
 				return -1;		/* NaN > number */
-			if (isnan(sb->distances[i]))
+			if (isnan(sb->distances[i].value))
 				return 1;		/* number < NaN */
-			if (sa->distances[i] != sb->distances[i])
-				return (sa->distances[i] < sb->distances[i]) ? 1 : -1;
+			if (sa->distances[i].value != sb->distances[i].value)
+				return (sa->distances[i].value < sb->distances[i].value) ? 1 : -1;
 		}
 	}
 
@@ -103,7 +113,8 @@ spgAddSearchItemToQueue(SpGistScanOpaque so, SpGistSearchItem *item)
 }
 
 static SpGistSearchItem *
-spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances)
+spgAllocSearchItem(SpGistScanOpaque so, bool isnull,
+				   IndexOrderByDistance *distances)
 {
 	/* allocate distance array only for non-NULL items */
 	SpGistSearchItem *item =
@@ -113,7 +124,7 @@ spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances)
 
 	if (!isnull && so->numberOfOrderBys > 0)
 		memcpy(item->distances, distances,
-			   so->numberOfOrderBys * sizeof(double));
+			   sizeof(item->distances[0]) * so->numberOfOrderBys);
 
 	return item;
 }
@@ -297,15 +308,17 @@ spgbeginscan(Relation rel, int keysz, int orderbysz)
 			palloc(sizeof(Oid) * scan->numberOfOrderBys);
 
 		/* These arrays have constant contents, so we can fill them now */
-		so->zeroDistances = (double *)
-			palloc(sizeof(double) * scan->numberOfOrderBys);
-		so->infDistances = (double *)
-			palloc(sizeof(double) * scan->numberOfOrderBys);
+		so->zeroDistances =
+			palloc(sizeof(so->zeroDistances[0]) * scan->numberOfOrderBys);
+		so->infDistances =
+			palloc(sizeof(so->infDistances[0]) * scan->numberOfOrderBys);
 
 		for (i = 0; i < scan->numberOfOrderBys; i++)
 		{
-			so->zeroDistances[i] = 0.0;
-			so->infDistances[i] = get_float8_infinity();
+			so->zeroDistances[i].value = 0.0;
+			so->zeroDistances[i].isnull = false;
+			so->infDistances[i].value = get_float8_infinity();
+			so->infDistances[i].isnull = false;
 		}
 
 		scan->xs_orderbyvals = (Datum *)
@@ -409,7 +422,7 @@ spgendscan(IndexScanDesc scan)
 static SpGistSearchItem *
 spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr,
 			   Datum leafValue, bool recheck, bool recheckDistances,
-			   bool isnull, double *distances)
+			   bool isnull, IndexOrderByDistance *distances)
 {
 	SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
 
@@ -439,7 +452,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 			bool *reportedSome, storeRes_func storeRes)
 {
 	Datum		leafValue;
-	double	   *distances;
+	IndexOrderByDistance *distances;
 	bool		result;
 	bool		recheck;
 	bool		recheckDistances;
@@ -549,7 +562,7 @@ spgMakeInnerItem(SpGistScanOpaque so,
 				 SpGistSearchItem *parentItem,
 				 SpGistNodeTuple tuple,
 				 spgInnerConsistentOut *out, int i, bool isnull,
-				 double *distances)
+				 IndexOrderByDistance *distances)
 {
 	SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
 
@@ -633,7 +646,7 @@ spgInnerTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		{
 			int			nodeN = out.nodeNumbers[i];
 			SpGistSearchItem *innerItem;
-			double	   *distances;
+			IndexOrderByDistance *distances;
 
 			Assert(nodeN >= 0 && nodeN < nNodes);
 
@@ -847,7 +860,7 @@ redirect:
 static void
 storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
 			Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			double *distances)
+			IndexOrderByDistance *distances)
 {
 	Assert(!recheckDistances && !distances);
 	tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
@@ -874,7 +887,7 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 static void
 storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 			  Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			  double *distances)
+			  IndexOrderByDistance *distances)
 {
 	Assert(so->nPtrs < MaxIndexTuplesPerPage);
 	so->heapPtrs[so->nPtrs] = *heapPtr;
@@ -887,9 +900,11 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 			so->distances[so->nPtrs] = NULL;
 		else
 		{
-			Size		size = sizeof(double) * so->numberOfOrderBys;
+			Size		size = sizeof(distances[0]) * so->numberOfOrderBys;
+
+			so->distances[so->nPtrs] = palloc(size);
 
-			so->distances[so->nPtrs] = memcpy(palloc(size), distances, size);
+			memcpy(so->distances[so->nPtrs], distances, size);
 		}
 	}
 
@@ -929,7 +944,6 @@ spggettuple(IndexScanDesc scan, ScanDirection dir)
 			if (so->numberOfOrderBys > 0)
 				index_store_float8_orderby_distances(scan, so->orderByTypes,
 													 so->distances[so->iPtr],
-													 NULL,
 													 so->recheckDistances[so->iPtr]);
 			so->iPtr++;
 			return true;
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index 8e29770..cde6264 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -580,24 +580,33 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 
 		if (in->norderbys > 0 && in->nNodes > 0)
 		{
-			double	   *distances = palloc(sizeof(double) * in->norderbys);
+			IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * in->norderbys);
 			int			j;
 
 			for (j = 0; j < in->norderbys; j++)
 			{
-				Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
+				if (in->orderbys[j].sk_flags & SK_ISNULL)
+				{
+					distances[j].value = 0;
+					distances[j].isnull = true;
+				}
+				else
+				{
+					Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
 
-				distances[j] = pointToRectBoxDistance(pt, rect_box);
+					distances[j].value = pointToRectBoxDistance(pt, rect_box);
+					distances[j].isnull = false;
+				}
 			}
 
-			out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+			out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 			out->distances[0] = distances;
 
 			for (i = 1; i < in->nNodes; i++)
 			{
-				out->distances[i] = palloc(sizeof(double) * in->norderbys);
+				out->distances[i] = palloc(sizeof(distances[0]) * in->norderbys);
 				memcpy(out->distances[i], distances,
-					   sizeof(double) * in->norderbys);
+					   sizeof(distances[0]) * in->norderbys);
 			}
 		}
 
@@ -622,7 +631,7 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
 	out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 	if (in->norderbys > 0)
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 
 	/*
 	 * We switch memory context, because we want to allocate memory for new
@@ -703,16 +712,25 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 
 			if (in->norderbys > 0)
 			{
-				double	   *distances = palloc(sizeof(double) * in->norderbys);
+				IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * in->norderbys);
 				int			j;
 
 				out->distances[out->nNodes] = distances;
 
 				for (j = 0; j < in->norderbys; j++)
 				{
-					Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
-
-					distances[j] = pointToRectBoxDistance(pt, next_rect_box);
+					if (in->orderbys[j].sk_flags & SK_ISNULL)
+					{
+						distances[j].value = 0;
+						distances[j].isnull = true;
+					}
+					else
+					{
+						Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
+
+						distances[j].value = pointToRectBoxDistance(pt, next_rect_box);
+						distances[j].isnull = false;
+					}
 				}
 			}
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 6c56717..aaa56ae 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -118,6 +118,13 @@ typedef enum IndexUniqueCheck
 } IndexUniqueCheck;
 
 
+/* Nullable ORDER BY distance */
+typedef struct IndexOrderByDistance
+{
+	double		value;
+	bool		isnull;
+} IndexOrderByDistance;
+
 /*
  * generalized index_ interface routines (in indexam.c)
  */
@@ -179,8 +186,7 @@ extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
 								   uint16 procnum);
 extern void index_store_float8_orderby_distances(IndexScanDesc scan,
 												 Oid *orderByTypes,
-												 double *distanceValues,
-												 bool *distanceNulls,
+												 IndexOrderByDistance *distances,
 												 bool recheckOrderBy);
 
 /*
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index ed5b643..ab134c1 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -138,29 +138,15 @@ typedef struct GISTSearchItem
 		GISTSearchHeapItem heap;	/* heap info, if heap tuple */
 	}			data;
 
-	/*
-	 * This data structure is followed by arrays of distance values and
-	 * distance null flags.  Size of both arrays is
-	 * IndexScanDesc->numberOfOrderBys. See macros below for accessing those
-	 * arrays.
-	 */
+	/* numberOfOrderBys entries */
+	IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER];
 } GISTSearchItem;
 
 #define GISTSearchItemIsHeap(item)	((item).blkno == InvalidBlockNumber)
 
-#define SizeOfGISTSearchItem(n_distances) (DOUBLEALIGN(sizeof(GISTSearchItem)) + \
-	(sizeof(double) + sizeof(bool)) * (n_distances))
-
-/*
- * We actually don't need n_distances compute pointer to distance values.
- * Nevertheless take n_distances as argument to have same arguments list for
- * GISTSearchItemDistanceValues() and GISTSearchItemDistanceNulls().
- */
-#define GISTSearchItemDistanceValues(item, n_distances) \
-	((double *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem))))
-
-#define GISTSearchItemDistanceNulls(item, n_distances) \
-	((bool *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem)) + sizeof(double) * (n_distances)))
+#define SizeOfGISTSearchItem(n_distances) \
+	(offsetof(GISTSearchItem, distances) + \
+	 sizeof(IndexOrderByDistance) * (n_distances))
 
 /*
  * GISTScanOpaqueData: private state for a scan of a GiST index
@@ -176,8 +162,7 @@ typedef struct GISTScanOpaqueData
 	bool		firstCall;		/* true until first gistgettuple call */
 
 	/* pre-allocated workspace arrays */
-	double	   *distanceValues; /* output area for gistindex_keytest */
-	bool	   *distanceNulls;
+	IndexOrderByDistance *distances;	/* output area for gistindex_keytest */
 
 	/* info about killed items if any (killedItems is NULL if never used) */
 	OffsetNumber *killedItems;	/* offset numbers of killed items */
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index d787ab2..bcbcb1b 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -161,7 +161,7 @@ typedef struct spgInnerConsistentOut
 	int		   *levelAdds;		/* increment level by this much for each */
 	Datum	   *reconstructedValues;	/* associated reconstructed values */
 	void	  **traversalValues;	/* opclass-specific traverse values */
-	double	  **distances;		/* associated distances */
+	IndexOrderByDistance **distances;	/* associated distances */
 } spgInnerConsistentOut;
 
 /*
@@ -188,7 +188,7 @@ typedef struct spgLeafConsistentOut
 	Datum		leafValue;		/* reconstructed original data, if any */
 	bool		recheck;		/* set true if operator must be rechecked */
 	bool		recheckDistances;	/* set true if distances must be rechecked */
-	double	   *distances;		/* associated distances */
+	IndexOrderByDistance *distances;	/* associated distances */
 } spgLeafConsistentOut;
 
 
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
index e0d1178..2875c1f 100644
--- a/src/include/access/spgist_private.h
+++ b/src/include/access/spgist_private.h
@@ -145,11 +145,12 @@ typedef struct SpGistSearchItem
 	bool		recheckDistances;	/* distance recheck is needed */
 
 	/* array with numberOfOrderBys entries */
-	double		distances[FLEXIBLE_ARRAY_MEMBER];
+	IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER];
 } SpGistSearchItem;
 
 #define SizeOfSpGistSearchItem(n_distances) \
-	(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances))
+	(offsetof(SpGistSearchItem, distances) + \
+	 sizeof(IndexOrderByDistance) * (n_distances))
 
 /*
  * Private state of an index scan
@@ -178,8 +179,8 @@ typedef struct SpGistScanOpaqueData
 	FmgrInfo	leafConsistentFn;
 
 	/* Pre-allocated workspace arrays: */
-	double	   *zeroDistances;
-	double	   *infDistances;
+	IndexOrderByDistance *zeroDistances;
+	IndexOrderByDistance *infDistances;
 
 	/* These fields are only used in amgetbitmap scans: */
 	TIDBitmap  *tbm;			/* bitmap being filled */
@@ -195,7 +196,9 @@ typedef struct SpGistScanOpaqueData
 	bool		recheckDistances[MaxIndexTuplesPerPage];	/* distance recheck
 															 * flags */
 	HeapTuple	reconTups[MaxIndexTuplesPerPage];	/* reconstructed tuples */
-	double	   *distances[MaxIndexTuplesPerPage];	/* distances (for recheck) */
+
+	/* distances (for recheck) */
+	IndexOrderByDistance *distances[MaxIndexTuplesPerPage];
 
 	/*
 	 * Note: using MaxIndexTuplesPerPage above is a bit hokey since
@@ -459,8 +462,9 @@ extern bool spgdoinsert(Relation index, SpGistState *state,
 						ItemPointer heapPtr, Datum datum, bool isnull);
 
 /* spgproc.c */
-extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
-										  ScanKey orderbys, int norderbys);
+extern IndexOrderByDistance *spg_key_orderbys_distances(Datum key, bool isLeaf,
+														ScanKey orderbys,
+														int norderbys);
 extern BOX *box_copy(BOX *orig);
 
 #endif							/* SPGIST_PRIVATE_H */
diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out
index 81b4a67..5ccea91 100644
--- a/src/test/regress/expected/create_index_spgist.out
+++ b/src/test/regress/expected/create_index_spgist.out
@@ -555,6 +555,16 @@ WHERE seq.dist IS DISTINCT FROM idx.dist;
 ---+------+---+---+------+---
 (0 rows)
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+      p      
+-------------
+ (59,21)
+ (9853,112)
+ (1239,5647)
+(3 rows)
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
                          QUERY PLAN                         
diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql
index 8e6c453..629936f 100644
--- a/src/test/regress/sql/create_index_spgist.sql
+++ b/src/test/regress/sql/create_index_spgist.sql
@@ -225,6 +225,11 @@ SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
 ON seq.n = idx.n
 WHERE seq.dist IS DISTINCT FROM idx.dist;
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
-- 
2.7.4


--------------E276088F2DBC9E82E62FE42F--





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH 1/4] Fix GiST and SP-GiST ordering by distance for NULLs
@ 2019-09-10 14:26  Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Nikita Glukhov @ 2019-09-10 14:26 UTC (permalink / raw)

---
 src/backend/access/gist/gistget.c                 |  68 ++++-------
 src/backend/access/gist/gistscan.c                |  18 ++-
 src/backend/access/index/indexam.c                |  22 ++--
 src/backend/access/spgist/spgkdtreeproc.c         |   2 +-
 src/backend/access/spgist/spgproc.c               |   9 +-
 src/backend/access/spgist/spgquadtreeproc.c       |   2 +-
 src/backend/access/spgist/spgscan.c               | 136 +++++++++++++++++-----
 src/backend/utils/adt/geo_spgist.c                |  18 +--
 src/include/access/genam.h                        |  10 +-
 src/include/access/gist_private.h                 |  27 +----
 src/include/access/spgist.h                       |   4 +-
 src/include/access/spgist_private.h               |  22 ++--
 src/test/regress/expected/create_index_spgist.out |  10 ++
 src/test/regress/sql/create_index_spgist.sql      |   5 +
 src/tools/pgindent/typedefs.list                  |   1 +
 15 files changed, 212 insertions(+), 142 deletions(-)

diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index db633a9..22d790d 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -112,9 +112,8 @@ gistkillitems(IndexScanDesc scan)
  * Similarly, *recheck_distances_p is set to indicate whether the distances
  * need to be rechecked, and it is also ignored for non-leaf entries.
  *
- * If we are doing an ordered scan, so->distancesValues[] and
- * so->distancesNulls[] is filled with distance data from the distance()
- * functions before returning success.
+ * If we are doing an ordered scan, so->distances[] is filled with distance
+ * data from the distance() functions before returning success.
  *
  * We must decompress the key in the IndexTuple before passing it to the
  * sk_funcs (which actually are the opclass Consistent or Distance methods).
@@ -135,8 +134,7 @@ gistindex_keytest(IndexScanDesc scan,
 	GISTSTATE  *giststate = so->giststate;
 	ScanKey		key = scan->keyData;
 	int			keySize = scan->numberOfKeys;
-	double	   *distance_value_p;
-	bool	   *distance_null_p;
+	IndexOrderByDistance *distance_p;
 	Relation	r = scan->indexRelation;
 
 	*recheck_p = false;
@@ -155,8 +153,8 @@ gistindex_keytest(IndexScanDesc scan,
 			elog(ERROR, "invalid GiST tuple found on leaf page");
 		for (i = 0; i < scan->numberOfOrderBys; i++)
 		{
-			so->distanceValues[i] = -get_float8_infinity();
-			so->distanceNulls[i] = false;
+			so->distances[i].value = -get_float8_infinity();
+			so->distances[i].isnull = false;
 		}
 		return true;
 	}
@@ -240,8 +238,7 @@ gistindex_keytest(IndexScanDesc scan,
 
 	/* OK, it passes --- now let's compute the distances */
 	key = scan->orderByData;
-	distance_value_p = so->distanceValues;
-	distance_null_p = so->distanceNulls;
+	distance_p = so->distances;
 	keySize = scan->numberOfOrderBys;
 	while (keySize > 0)
 	{
@@ -256,8 +253,8 @@ gistindex_keytest(IndexScanDesc scan,
 		if ((key->sk_flags & SK_ISNULL) || isNull)
 		{
 			/* Assume distance computes as null */
-			*distance_value_p = 0.0;
-			*distance_null_p = true;
+			distance_p->value = 0.0;
+			distance_p->isnull = true;
 		}
 		else
 		{
@@ -294,13 +291,12 @@ gistindex_keytest(IndexScanDesc scan,
 									 ObjectIdGetDatum(key->sk_subtype),
 									 PointerGetDatum(&recheck));
 			*recheck_distances_p |= recheck;
-			*distance_value_p = DatumGetFloat8(dist);
-			*distance_null_p = false;
+			distance_p->value = DatumGetFloat8(dist);
+			distance_p->isnull = false;
 		}
 
 		key++;
-		distance_value_p++;
-		distance_null_p++;
+		distance_p++;
 		keySize--;
 	}
 
@@ -313,8 +309,7 @@ gistindex_keytest(IndexScanDesc scan,
  *
  * scan: index scan we are executing
  * pageItem: search queue item identifying an index page to scan
- * myDistanceValues: distances array associated with pageItem, or NULL at the root
- * myDistanceNulls: null flags for myDistanceValues array, or NULL at the root
+ * myDistances: distances array associated with pageItem, or NULL at the root
  * tbm: if not NULL, gistgetbitmap's output bitmap
  * ntids: if not NULL, gistgetbitmap's output tuple counter
  *
@@ -332,8 +327,7 @@ gistindex_keytest(IndexScanDesc scan,
  */
 static void
 gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
-			 double *myDistanceValues, bool *myDistanceNulls,
-			 TIDBitmap *tbm, int64 *ntids)
+			 IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
 {
 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
 	GISTSTATE  *giststate = so->giststate;
@@ -370,7 +364,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		GISTSearchItem *item;
 
 		/* This can't happen when starting at the root */
-		Assert(myDistanceValues != NULL && myDistanceNulls != NULL);
+		Assert(myDistances != NULL);
 
 		oldcxt = MemoryContextSwitchTo(so->queueCxt);
 
@@ -380,10 +374,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		item->data.parentlsn = pageItem->data.parentlsn;
 
 		/* Insert it into the queue using same distances as for this page */
-		memcpy(GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-			   myDistanceValues, sizeof(double) * scan->numberOfOrderBys);
-		memcpy(GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-			   myDistanceNulls, sizeof(bool) * scan->numberOfOrderBys);
+		memcpy(item->distances, myDistances,
+			   sizeof(item->distances[0]) * scan->numberOfOrderBys);
 
 		pairingheap_add(so->queue, &item->phNode);
 
@@ -527,10 +519,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 			}
 
 			/* Insert it into the queue using new distance data */
-			memcpy(GISTSearchItemDistanceValues(item, nOrderBys),
-				   so->distanceValues, sizeof(double) * nOrderBys);
-			memcpy(GISTSearchItemDistanceNulls(item, nOrderBys),
-				   so->distanceNulls, sizeof(bool) * nOrderBys);
+			memcpy(item->distances, so->distances,
+				   sizeof(item->distances[0]) * nOrderBys);
 
 			pairingheap_add(so->queue, &item->phNode);
 
@@ -595,8 +585,7 @@ getNextNearest(IndexScanDesc scan)
 			scan->xs_recheck = item->data.heap.recheck;
 
 			index_store_float8_orderby_distances(scan, so->orderByTypes,
-												 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-												 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
+												 item->distances,
 												 item->data.heap.recheckDistances);
 
 			/* in an index-only scan, also return the reconstructed tuple. */
@@ -609,10 +598,7 @@ getNextNearest(IndexScanDesc scan)
 			/* visit an index page, extract its items into queue */
 			CHECK_FOR_INTERRUPTS();
 
-			gistScanPage(scan, item,
-						 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-						 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-						 NULL, NULL);
+			gistScanPage(scan, item, item->distances, NULL, NULL);
 		}
 
 		pfree(item);
@@ -650,7 +636,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 
 		fakeItem.blkno = GIST_ROOT_BLKNO;
 		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-		gistScanPage(scan, &fakeItem, NULL, NULL, NULL, NULL);
+		gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
 	}
 
 	if (scan->numberOfOrderBys > 0)
@@ -744,10 +730,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * this page, we fall out of the inner "do" and loop around to
 				 * return them.
 				 */
-				gistScanPage(scan, item,
-							 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-							 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-							 NULL, NULL);
+				gistScanPage(scan, item, item->distances, NULL, NULL);
 
 				pfree(item);
 			} while (so->nPageData == 0);
@@ -778,7 +761,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 	fakeItem.blkno = GIST_ROOT_BLKNO;
 	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-	gistScanPage(scan, &fakeItem, NULL, NULL, tbm, &ntids);
+	gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
 
 	/*
 	 * While scanning a leaf page, ItemPointers of matching heap tuples will
@@ -793,10 +776,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 		CHECK_FOR_INTERRUPTS();
 
-		gistScanPage(scan, item,
-					 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-					 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-					 tbm, &ntids);
+		gistScanPage(scan, item, item->distances, tbm, &ntids);
 
 		pfree(item);
 	}
diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c
index e72bf08..323a19d 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -14,6 +14,8 @@
  */
 #include "postgres.h"
 
+#include <math.h>
+
 #include "access/gist_private.h"
 #include "access/gistscan.h"
 #include "access/relscan.h"
@@ -33,26 +35,23 @@ pairingheap_GISTSearchItem_cmp(const pairingheap_node *a, const pairingheap_node
 	const GISTSearchItem *sb = (const GISTSearchItem *) b;
 	IndexScanDesc scan = (IndexScanDesc) arg;
 	int			i;
-	double	   *da = GISTSearchItemDistanceValues(sa, scan->numberOfOrderBys),
-			   *db = GISTSearchItemDistanceValues(sb, scan->numberOfOrderBys);
-	bool	   *na = GISTSearchItemDistanceNulls(sa, scan->numberOfOrderBys),
-			   *nb = GISTSearchItemDistanceNulls(sb, scan->numberOfOrderBys);
 
 	/* Order according to distance comparison */
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (na[i])
+		if (sa->distances[i].isnull)
 		{
-			if (!nb[i])
+			if (!sb->distances[i].isnull)
 				return -1;
 		}
-		else if (nb[i])
+		else if (sb->distances[i].isnull)
 		{
 			return 1;
 		}
 		else
 		{
-			int			cmp = -float8_cmp_internal(da[i], db[i]);
+			int			cmp = -float8_cmp_internal(sa->distances[i].value,
+												   sb->distances[i].value);
 
 			if (cmp != 0)
 				return cmp;
@@ -100,8 +99,7 @@ gistbeginscan(Relation r, int nkeys, int norderbys)
 	so->queueCxt = giststate->scanCxt;	/* see gistrescan */
 
 	/* workspaces with size dependent on numberOfOrderBys: */
-	so->distanceValues = palloc(sizeof(double) * scan->numberOfOrderBys);
-	so->distanceNulls = palloc(sizeof(bool) * scan->numberOfOrderBys);
+	so->distances = palloc(sizeof(so->distances[0]) * scan->numberOfOrderBys);
 	so->qual_ok = true;			/* in case there are zero keys */
 	if (scan->numberOfOrderBys > 0)
 	{
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 2e8f53a..442f414 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -847,14 +847,14 @@ index_getprocinfo(Relation irel,
  */
 void
 index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
-									 double *distanceValues,
-									 bool *distanceNulls, bool recheckOrderBy)
+									 IndexOrderByDistance *distances,
+									 bool recheckOrderBy)
 {
 	int			i;
 
 	scan->xs_recheckorderby = recheckOrderBy;
 
-	if (!distanceValues)
+	if (!distances)
 	{
 		Assert(!scan->xs_recheckorderby);
 
@@ -869,11 +869,11 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (distanceNulls && distanceNulls[i])
-		{
+		scan->xs_orderbynulls[i] = distances[i].isnull;
+
+		if (scan->xs_orderbynulls[i])
 			scan->xs_orderbyvals[i] = (Datum) 0;
-			scan->xs_orderbynulls[i] = true;
-		}
+
 		if (orderByTypes[i] == FLOAT8OID)
 		{
 #ifndef USE_FLOAT8_BYVAL
@@ -881,8 +881,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float8GetDatum(distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float8GetDatum(distances[i].value);
 		}
 		else if (orderByTypes[i] == FLOAT4OID)
 		{
@@ -892,8 +892,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float4GetDatum((float4) distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float4GetDatum((float4) distances[i].value);
 		}
 		else
 		{
diff --git a/src/backend/access/spgist/spgkdtreeproc.c b/src/backend/access/spgist/spgkdtreeproc.c
index 9d479fe..66ab1ac 100644
--- a/src/backend/access/spgist/spgkdtreeproc.c
+++ b/src/backend/access/spgist/spgkdtreeproc.c
@@ -271,7 +271,7 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
 		BOX			infArea;
 		BOX		   *area;
 
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 
 		if (in->level == 0)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 688638a..41203ed 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -59,20 +59,21 @@ point_box_distance(Point *point, BOX *box)
  * is expected to be point, non-leaf key is expected to be box.  Scan key
  * arguments are expected to be points.
  */
-double *
+IndexOrderByDistance *
 spg_key_orderbys_distances(Datum key, bool isLeaf,
 						   ScanKey orderbys, int norderbys)
 {
 	int			sk_num;
-	double	   *distances = (double *) palloc(norderbys * sizeof(double)),
+	IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * norderbys),
 			   *distance = distances;
 
 	for (sk_num = 0; sk_num < norderbys; ++sk_num, ++orderbys, ++distance)
 	{
 		Point	   *point = DatumGetPointP(orderbys->sk_argument);
 
-		*distance = isLeaf ? point_point_distance(point, DatumGetPointP(key))
-			: point_box_distance(point, DatumGetBoxP(key));
+		distance->isnull = false;
+		distance->value = isLeaf ? point_point_distance(point, DatumGetPointP(key))
+				: point_box_distance(point, DatumGetBoxP(key));
 	}
 
 	return distances;
diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index e50108e..3f59bea 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -247,7 +247,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 	 */
 	if (in->norderbys > 0)
 	{
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 
 		if (in->level == 0)
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2bd4037..41354d8e 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -28,7 +28,8 @@
 
 typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
 							   Datum leafValue, bool isNull, bool recheck,
-							   bool recheckDistances, double *distances);
+							   bool recheckDistances,
+							   IndexOrderByDistance *distances);
 
 /*
  * Pairing heap comparison function for the SpGistSearchItem queue.
@@ -56,16 +57,25 @@ pairingheap_SpGistSearchItem_cmp(const pairingheap_node *a,
 	else
 	{
 		/* Order according to distance comparison */
-		for (i = 0; i < so->numberOfOrderBys; i++)
+		for (i = 0; i < so->numberOfNonNullOrderBys; i++)
 		{
-			if (isnan(sa->distances[i]) && isnan(sb->distances[i]))
+			if (sa->distances[i].isnull)
+			{
+				if (!sb->distances[i].isnull)
+					return -1;
+				continue;
+			}
+			else if (sb->distances[i].isnull)
+				return 1;
+
+			if (isnan(sa->distances[i].value) && isnan(sb->distances[i].value))
 				continue;		/* NaN == NaN */
-			if (isnan(sa->distances[i]))
+			if (isnan(sa->distances[i].value))
 				return -1;		/* NaN > number */
-			if (isnan(sb->distances[i]))
+			if (isnan(sb->distances[i].value))
 				return 1;		/* number < NaN */
-			if (sa->distances[i] != sb->distances[i])
-				return (sa->distances[i] < sb->distances[i]) ? 1 : -1;
+			if (sa->distances[i].value != sb->distances[i].value)
+				return (sa->distances[i].value < sb->distances[i].value) ? 1 : -1;
 		}
 	}
 
@@ -103,17 +113,18 @@ spgAddSearchItemToQueue(SpGistScanOpaque so, SpGistSearchItem *item)
 }
 
 static SpGistSearchItem *
-spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances)
+spgAllocSearchItem(SpGistScanOpaque so, bool isnull,
+				   IndexOrderByDistance *distances)
 {
 	/* allocate distance array only for non-NULL items */
 	SpGistSearchItem *item =
-	palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfOrderBys));
+	palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfNonNullOrderBys));
 
 	item->isNull = isnull;
 
-	if (!isnull && so->numberOfOrderBys > 0)
+	if (!isnull && so->numberOfNonNullOrderBys > 0)
 		memcpy(item->distances, distances,
-			   so->numberOfOrderBys * sizeof(double));
+			   sizeof(item->distances[0]) * so->numberOfNonNullOrderBys);
 
 	return item;
 }
@@ -208,6 +219,34 @@ spgPrepareScanKeys(IndexScanDesc scan)
 	so->numberOfOrderBys = scan->numberOfOrderBys;
 	so->orderByData = scan->orderByData;
 
+	if (so->numberOfOrderBys <= 0)
+		so->numberOfNonNullOrderBys = 0;
+	else
+	{
+		int			j = 0;
+
+		/*
+		 * Remove all NULL keys, but remember their offsets in the original
+		 * array.
+		 */
+		for (i = 0; i < scan->numberOfOrderBys; i++)
+		{
+			ScanKey		skey = &so->orderByData[i];
+
+			if (skey->sk_flags & SK_ISNULL)
+				so->nonNullOrderByOffsets[i] = -1;
+			else
+			{
+				if (i != j)
+					so->orderByData[j] = *skey;
+
+				so->nonNullOrderByOffsets[i] = j++;
+			}
+		}
+
+		so->numberOfNonNullOrderBys = j;
+	}
+
 	if (scan->numberOfKeys <= 0)
 	{
 		/* If no quals, whole-index scan is required */
@@ -295,17 +334,21 @@ spgbeginscan(Relation rel, int keysz, int orderbysz)
 		/* This will be filled in spgrescan, but allocate the space here */
 		so->orderByTypes = (Oid *)
 			palloc(sizeof(Oid) * scan->numberOfOrderBys);
+		so->nonNullOrderByOffsets = (int *)
+			palloc(sizeof(int) * scan->numberOfOrderBys);
 
 		/* These arrays have constant contents, so we can fill them now */
-		so->zeroDistances = (double *)
-			palloc(sizeof(double) * scan->numberOfOrderBys);
-		so->infDistances = (double *)
-			palloc(sizeof(double) * scan->numberOfOrderBys);
+		so->zeroDistances =
+			palloc(sizeof(so->zeroDistances[0]) * scan->numberOfOrderBys);
+		so->infDistances =
+			palloc(sizeof(so->infDistances[0]) * scan->numberOfOrderBys);
 
 		for (i = 0; i < scan->numberOfOrderBys; i++)
 		{
-			so->zeroDistances[i] = 0.0;
-			so->infDistances[i] = get_float8_infinity();
+			so->zeroDistances[i].value = 0.0;
+			so->zeroDistances[i].isnull = false;
+			so->infDistances[i].value = get_float8_infinity();
+			so->infDistances[i].isnull = false;
 		}
 
 		scan->xs_orderbyvals = (Datum *)
@@ -394,6 +437,7 @@ spgendscan(IndexScanDesc scan)
 	if (scan->numberOfOrderBys > 0)
 	{
 		pfree(so->orderByTypes);
+		pfree(so->nonNullOrderByOffsets);
 		pfree(so->zeroDistances);
 		pfree(so->infDistances);
 		pfree(scan->xs_orderbyvals);
@@ -409,7 +453,7 @@ spgendscan(IndexScanDesc scan)
 static SpGistSearchItem *
 spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr,
 			   Datum leafValue, bool recheck, bool recheckDistances,
-			   bool isnull, double *distances)
+			   bool isnull, IndexOrderByDistance *distances)
 {
 	SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
 
@@ -439,7 +483,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 			bool *reportedSome, storeRes_func storeRes)
 {
 	Datum		leafValue;
-	double	   *distances;
+	IndexOrderByDistance *distances;
 	bool		result;
 	bool		recheck;
 	bool		recheckDistances;
@@ -465,7 +509,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		in.scankeys = so->keyData;
 		in.nkeys = so->numberOfKeys;
 		in.orderbys = so->orderByData;
-		in.norderbys = so->numberOfOrderBys;
+		in.norderbys = so->numberOfNonNullOrderBys;
 		in.reconstructedValue = item->value;
 		in.traversalValue = item->traversalValue;
 		in.level = item->level;
@@ -492,7 +536,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 	if (result)
 	{
 		/* item passes the scankeys */
-		if (so->numberOfOrderBys > 0)
+		if (so->numberOfNonNullOrderBys > 0)
 		{
 			/* the scan is ordered -> add the item to the queue */
 			MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt);
@@ -531,7 +575,7 @@ spgInitInnerConsistentIn(spgInnerConsistentIn *in,
 	in->scankeys = so->keyData;
 	in->orderbys = so->orderByData;
 	in->nkeys = so->numberOfKeys;
-	in->norderbys = so->numberOfOrderBys;
+	in->norderbys = so->numberOfNonNullOrderBys;
 	in->reconstructedValue = item->value;
 	in->traversalMemoryContext = so->traversalCxt;
 	in->traversalValue = item->traversalValue;
@@ -549,7 +593,7 @@ spgMakeInnerItem(SpGistScanOpaque so,
 				 SpGistSearchItem *parentItem,
 				 SpGistNodeTuple tuple,
 				 spgInnerConsistentOut *out, int i, bool isnull,
-				 double *distances)
+				 IndexOrderByDistance *distances)
 {
 	SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
 
@@ -633,7 +677,7 @@ spgInnerTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		{
 			int			nodeN = out.nodeNumbers[i];
 			SpGistSearchItem *innerItem;
-			double	   *distances;
+			IndexOrderByDistance *distances;
 
 			Assert(nodeN >= 0 && nodeN < nNodes);
 
@@ -751,7 +795,7 @@ redirect:
 		if (item->isLeaf)
 		{
 			/* We store heap items in the queue only in case of ordered search */
-			Assert(so->numberOfOrderBys > 0);
+			Assert(so->numberOfNonNullOrderBys > 0);
 			storeRes(so, &item->heapPtr, item->value, item->isNull,
 					 item->recheck, item->recheckDistances, item->distances);
 			reportedSome = true;
@@ -847,7 +891,7 @@ redirect:
 static void
 storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
 			Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			double *distances)
+			IndexOrderByDistance *distances)
 {
 	Assert(!recheckDistances && !distances);
 	tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
@@ -874,7 +918,7 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 static void
 storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 			  Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			  double *distances)
+			  IndexOrderByDistance *nonNullDistances)
 {
 	Assert(so->nPtrs < MaxIndexTuplesPerPage);
 	so->heapPtrs[so->nPtrs] = *heapPtr;
@@ -883,13 +927,44 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 
 	if (so->numberOfOrderBys > 0)
 	{
-		if (isnull)
+		if (isnull || so->numberOfNonNullOrderBys <= 0)
 			so->distances[so->nPtrs] = NULL;
 		else
 		{
-			Size		size = sizeof(double) * so->numberOfOrderBys;
+			Size		size = sizeof(nonNullDistances[0]) * so->numberOfOrderBys;
+			IndexOrderByDistance *distances = palloc(size);
+
+			if (so->numberOfNonNullOrderBys >= so->numberOfOrderBys)
+			{
+				/*
+				 * All distance keys are not NULL, so simply copy distance
+				 * values.
+				 */
+				memcpy(distances, nonNullDistances, size);
+			}
+			else
+			{
+				int			i;
+
+				for (i = 0; i < so->numberOfOrderBys; i++)
+				{
+					int			offset = so->nonNullOrderByOffsets[i];
+
+					if (offset >= 0)
+					{
+						/* Copy non-NULL distance value */
+						distances[i] = nonNullDistances[offset];
+					}
+					else
+					{
+						/* Set distance's NULL flag. */
+						distances[i].value = 0.0;
+						distances[i].isnull = true;
+					}
+				}
+			}
 
-			so->distances[so->nPtrs] = memcpy(palloc(size), distances, size);
+			so->distances[so->nPtrs] = distances;
 		}
 	}
 
@@ -929,7 +1004,6 @@ spggettuple(IndexScanDesc scan, ScanDirection dir)
 			if (so->numberOfOrderBys > 0)
 				index_store_float8_orderby_distances(scan, so->orderByTypes,
 													 so->distances[so->iPtr],
-													 NULL,
 													 so->recheckDistances[so->iPtr]);
 			so->iPtr++;
 			return true;
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index 8e29770..448b574 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -580,24 +580,25 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 
 		if (in->norderbys > 0 && in->nNodes > 0)
 		{
-			double	   *distances = palloc(sizeof(double) * in->norderbys);
+			IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * in->norderbys);
 			int			j;
 
 			for (j = 0; j < in->norderbys; j++)
 			{
 				Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
 
-				distances[j] = pointToRectBoxDistance(pt, rect_box);
+				distances[j].value = pointToRectBoxDistance(pt, rect_box);
+				distances[j].isnull = false;
 			}
 
-			out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+			out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 			out->distances[0] = distances;
 
 			for (i = 1; i < in->nNodes; i++)
 			{
-				out->distances[i] = palloc(sizeof(double) * in->norderbys);
+				out->distances[i] = palloc(sizeof(distances[0]) * in->norderbys);
 				memcpy(out->distances[i], distances,
-					   sizeof(double) * in->norderbys);
+					   sizeof(distances[0]) * in->norderbys);
 			}
 		}
 
@@ -622,7 +623,7 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
 	out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
 	if (in->norderbys > 0)
-		out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
+		out->distances = palloc(sizeof(out->distances[0]) * in->nNodes);
 
 	/*
 	 * We switch memory context, because we want to allocate memory for new
@@ -703,7 +704,7 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 
 			if (in->norderbys > 0)
 			{
-				double	   *distances = palloc(sizeof(double) * in->norderbys);
+				IndexOrderByDistance *distances = palloc(sizeof(distances[0]) * in->norderbys);
 				int			j;
 
 				out->distances[out->nNodes] = distances;
@@ -712,7 +713,8 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
 				{
 					Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument);
 
-					distances[j] = pointToRectBoxDistance(pt, next_rect_box);
+					distances[j].value = pointToRectBoxDistance(pt, next_rect_box);
+					distances[j].isnull = false;
 				}
 			}
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 6c56717..aaa56ae 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -118,6 +118,13 @@ typedef enum IndexUniqueCheck
 } IndexUniqueCheck;
 
 
+/* Nullable ORDER BY distance */
+typedef struct IndexOrderByDistance
+{
+	double		value;
+	bool		isnull;
+} IndexOrderByDistance;
+
 /*
  * generalized index_ interface routines (in indexam.c)
  */
@@ -179,8 +186,7 @@ extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
 								   uint16 procnum);
 extern void index_store_float8_orderby_distances(IndexScanDesc scan,
 												 Oid *orderByTypes,
-												 double *distanceValues,
-												 bool *distanceNulls,
+												 IndexOrderByDistance *distances,
 												 bool recheckOrderBy);
 
 /*
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index ed5b643..ab134c1 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -138,29 +138,15 @@ typedef struct GISTSearchItem
 		GISTSearchHeapItem heap;	/* heap info, if heap tuple */
 	}			data;
 
-	/*
-	 * This data structure is followed by arrays of distance values and
-	 * distance null flags.  Size of both arrays is
-	 * IndexScanDesc->numberOfOrderBys. See macros below for accessing those
-	 * arrays.
-	 */
+	/* numberOfOrderBys entries */
+	IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER];
 } GISTSearchItem;
 
 #define GISTSearchItemIsHeap(item)	((item).blkno == InvalidBlockNumber)
 
-#define SizeOfGISTSearchItem(n_distances) (DOUBLEALIGN(sizeof(GISTSearchItem)) + \
-	(sizeof(double) + sizeof(bool)) * (n_distances))
-
-/*
- * We actually don't need n_distances compute pointer to distance values.
- * Nevertheless take n_distances as argument to have same arguments list for
- * GISTSearchItemDistanceValues() and GISTSearchItemDistanceNulls().
- */
-#define GISTSearchItemDistanceValues(item, n_distances) \
-	((double *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem))))
-
-#define GISTSearchItemDistanceNulls(item, n_distances) \
-	((bool *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem)) + sizeof(double) * (n_distances)))
+#define SizeOfGISTSearchItem(n_distances) \
+	(offsetof(GISTSearchItem, distances) + \
+	 sizeof(IndexOrderByDistance) * (n_distances))
 
 /*
  * GISTScanOpaqueData: private state for a scan of a GiST index
@@ -176,8 +162,7 @@ typedef struct GISTScanOpaqueData
 	bool		firstCall;		/* true until first gistgettuple call */
 
 	/* pre-allocated workspace arrays */
-	double	   *distanceValues; /* output area for gistindex_keytest */
-	bool	   *distanceNulls;
+	IndexOrderByDistance *distances;	/* output area for gistindex_keytest */
 
 	/* info about killed items if any (killedItems is NULL if never used) */
 	OffsetNumber *killedItems;	/* offset numbers of killed items */
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index d787ab2..bcbcb1b 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -161,7 +161,7 @@ typedef struct spgInnerConsistentOut
 	int		   *levelAdds;		/* increment level by this much for each */
 	Datum	   *reconstructedValues;	/* associated reconstructed values */
 	void	  **traversalValues;	/* opclass-specific traverse values */
-	double	  **distances;		/* associated distances */
+	IndexOrderByDistance **distances;	/* associated distances */
 } spgInnerConsistentOut;
 
 /*
@@ -188,7 +188,7 @@ typedef struct spgLeafConsistentOut
 	Datum		leafValue;		/* reconstructed original data, if any */
 	bool		recheck;		/* set true if operator must be rechecked */
 	bool		recheckDistances;	/* set true if distances must be rechecked */
-	double	   *distances;		/* associated distances */
+	IndexOrderByDistance *distances;	/* associated distances */
 } spgLeafConsistentOut;
 
 
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
index e0d1178..1bfe7fa 100644
--- a/src/include/access/spgist_private.h
+++ b/src/include/access/spgist_private.h
@@ -145,11 +145,12 @@ typedef struct SpGistSearchItem
 	bool		recheckDistances;	/* distance recheck is needed */
 
 	/* array with numberOfOrderBys entries */
-	double		distances[FLEXIBLE_ARRAY_MEMBER];
+	IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER];
 } SpGistSearchItem;
 
 #define SizeOfSpGistSearchItem(n_distances) \
-	(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances))
+	(offsetof(SpGistSearchItem, distances) + \
+	 sizeof(IndexOrderByDistance) * (n_distances))
 
 /*
  * Private state of an index scan
@@ -169,8 +170,12 @@ typedef struct SpGistScanOpaqueData
 	int			numberOfKeys;	/* number of index qualifier conditions */
 	ScanKey		keyData;		/* array of index qualifier descriptors */
 	int			numberOfOrderBys;	/* number of ordering operators */
+	int			numberOfNonNullOrderBys;	/* number of ordering operators
+											 * with non-NULL arguments */
 	ScanKey		orderByData;	/* array of ordering op descriptors */
 	Oid		   *orderByTypes;	/* array of ordering op return types */
+	int		   *nonNullOrderByOffsets;	/* array of offset of non-NULL ordering
+										 * keys in the original array */
 	Oid			indexCollation; /* collation of index column */
 
 	/* Opclass defined functions: */
@@ -178,8 +183,8 @@ typedef struct SpGistScanOpaqueData
 	FmgrInfo	leafConsistentFn;
 
 	/* Pre-allocated workspace arrays: */
-	double	   *zeroDistances;
-	double	   *infDistances;
+	IndexOrderByDistance *zeroDistances;
+	IndexOrderByDistance *infDistances;
 
 	/* These fields are only used in amgetbitmap scans: */
 	TIDBitmap  *tbm;			/* bitmap being filled */
@@ -195,7 +200,9 @@ typedef struct SpGistScanOpaqueData
 	bool		recheckDistances[MaxIndexTuplesPerPage];	/* distance recheck
 															 * flags */
 	HeapTuple	reconTups[MaxIndexTuplesPerPage];	/* reconstructed tuples */
-	double	   *distances[MaxIndexTuplesPerPage];	/* distances (for recheck) */
+
+	/* distances (for recheck) */
+	IndexOrderByDistance *distances[MaxIndexTuplesPerPage];
 
 	/*
 	 * Note: using MaxIndexTuplesPerPage above is a bit hokey since
@@ -459,8 +466,9 @@ extern bool spgdoinsert(Relation index, SpGistState *state,
 						ItemPointer heapPtr, Datum datum, bool isnull);
 
 /* spgproc.c */
-extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
-										  ScanKey orderbys, int norderbys);
+extern IndexOrderByDistance *spg_key_orderbys_distances(Datum key, bool isLeaf,
+														ScanKey orderbys,
+														int norderbys);
 extern BOX *box_copy(BOX *orig);
 
 #endif							/* SPGIST_PRIVATE_H */
diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out
index 81b4a67..5ccea91 100644
--- a/src/test/regress/expected/create_index_spgist.out
+++ b/src/test/regress/expected/create_index_spgist.out
@@ -555,6 +555,16 @@ WHERE seq.dist IS DISTINCT FROM idx.dist;
 ---+------+---+---+------+---
 (0 rows)
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+      p      
+-------------
+ (59,21)
+ (9853,112)
+ (1239,5647)
+(3 rows)
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
                          QUERY PLAN                         
diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql
index 8e6c453..629936f 100644
--- a/src/test/regress/sql/create_index_spgist.sql
+++ b/src/test/regress/sql/create_index_spgist.sql
@@ -225,6 +225,11 @@ SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
 ON seq.n = idx.n
 WHERE seq.dist IS DISTINCT FROM idx.dist;
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3cdfa8..fb65265 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1043,6 +1043,7 @@ IndexList
 IndexOnlyScan
 IndexOnlyScanState
 IndexOptInfo
+IndexOrderByDistance
 IndexPath
 IndexRuntimeKeyInfo
 IndexScan
-- 
2.7.4


--------------E0A4FE4CE26F0B5C9EE7D9CB--






^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH 1/5] Fix GiST and SP-GiST ordering by distance for NULLs
@ 2019-09-10 14:26  Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Nikita Glukhov @ 2019-09-10 14:26 UTC (permalink / raw)

---
 src/backend/access/gist/gistget.c                 | 68 ++++++++-------------
 src/backend/access/gist/gistscan.c                | 18 +++---
 src/backend/access/index/indexam.c                | 22 +++----
 src/backend/access/spgist/spgscan.c               | 74 +++++++++++++++++++----
 src/include/access/genam.h                        | 10 ++-
 src/include/access/gist_private.h                 | 27 ++-------
 src/include/access/spgist_private.h               |  8 ++-
 src/test/regress/expected/create_index_spgist.out | 10 +++
 src/test/regress/sql/create_index_spgist.sql      |  5 ++
 src/tools/pgindent/typedefs.list                  |  1 +
 10 files changed, 142 insertions(+), 101 deletions(-)

diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index db633a9..22d790d 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -112,9 +112,8 @@ gistkillitems(IndexScanDesc scan)
  * Similarly, *recheck_distances_p is set to indicate whether the distances
  * need to be rechecked, and it is also ignored for non-leaf entries.
  *
- * If we are doing an ordered scan, so->distancesValues[] and
- * so->distancesNulls[] is filled with distance data from the distance()
- * functions before returning success.
+ * If we are doing an ordered scan, so->distances[] is filled with distance
+ * data from the distance() functions before returning success.
  *
  * We must decompress the key in the IndexTuple before passing it to the
  * sk_funcs (which actually are the opclass Consistent or Distance methods).
@@ -135,8 +134,7 @@ gistindex_keytest(IndexScanDesc scan,
 	GISTSTATE  *giststate = so->giststate;
 	ScanKey		key = scan->keyData;
 	int			keySize = scan->numberOfKeys;
-	double	   *distance_value_p;
-	bool	   *distance_null_p;
+	IndexOrderByDistance *distance_p;
 	Relation	r = scan->indexRelation;
 
 	*recheck_p = false;
@@ -155,8 +153,8 @@ gistindex_keytest(IndexScanDesc scan,
 			elog(ERROR, "invalid GiST tuple found on leaf page");
 		for (i = 0; i < scan->numberOfOrderBys; i++)
 		{
-			so->distanceValues[i] = -get_float8_infinity();
-			so->distanceNulls[i] = false;
+			so->distances[i].value = -get_float8_infinity();
+			so->distances[i].isnull = false;
 		}
 		return true;
 	}
@@ -240,8 +238,7 @@ gistindex_keytest(IndexScanDesc scan,
 
 	/* OK, it passes --- now let's compute the distances */
 	key = scan->orderByData;
-	distance_value_p = so->distanceValues;
-	distance_null_p = so->distanceNulls;
+	distance_p = so->distances;
 	keySize = scan->numberOfOrderBys;
 	while (keySize > 0)
 	{
@@ -256,8 +253,8 @@ gistindex_keytest(IndexScanDesc scan,
 		if ((key->sk_flags & SK_ISNULL) || isNull)
 		{
 			/* Assume distance computes as null */
-			*distance_value_p = 0.0;
-			*distance_null_p = true;
+			distance_p->value = 0.0;
+			distance_p->isnull = true;
 		}
 		else
 		{
@@ -294,13 +291,12 @@ gistindex_keytest(IndexScanDesc scan,
 									 ObjectIdGetDatum(key->sk_subtype),
 									 PointerGetDatum(&recheck));
 			*recheck_distances_p |= recheck;
-			*distance_value_p = DatumGetFloat8(dist);
-			*distance_null_p = false;
+			distance_p->value = DatumGetFloat8(dist);
+			distance_p->isnull = false;
 		}
 
 		key++;
-		distance_value_p++;
-		distance_null_p++;
+		distance_p++;
 		keySize--;
 	}
 
@@ -313,8 +309,7 @@ gistindex_keytest(IndexScanDesc scan,
  *
  * scan: index scan we are executing
  * pageItem: search queue item identifying an index page to scan
- * myDistanceValues: distances array associated with pageItem, or NULL at the root
- * myDistanceNulls: null flags for myDistanceValues array, or NULL at the root
+ * myDistances: distances array associated with pageItem, or NULL at the root
  * tbm: if not NULL, gistgetbitmap's output bitmap
  * ntids: if not NULL, gistgetbitmap's output tuple counter
  *
@@ -332,8 +327,7 @@ gistindex_keytest(IndexScanDesc scan,
  */
 static void
 gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
-			 double *myDistanceValues, bool *myDistanceNulls,
-			 TIDBitmap *tbm, int64 *ntids)
+			 IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
 {
 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
 	GISTSTATE  *giststate = so->giststate;
@@ -370,7 +364,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		GISTSearchItem *item;
 
 		/* This can't happen when starting at the root */
-		Assert(myDistanceValues != NULL && myDistanceNulls != NULL);
+		Assert(myDistances != NULL);
 
 		oldcxt = MemoryContextSwitchTo(so->queueCxt);
 
@@ -380,10 +374,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 		item->data.parentlsn = pageItem->data.parentlsn;
 
 		/* Insert it into the queue using same distances as for this page */
-		memcpy(GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-			   myDistanceValues, sizeof(double) * scan->numberOfOrderBys);
-		memcpy(GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-			   myDistanceNulls, sizeof(bool) * scan->numberOfOrderBys);
+		memcpy(item->distances, myDistances,
+			   sizeof(item->distances[0]) * scan->numberOfOrderBys);
 
 		pairingheap_add(so->queue, &item->phNode);
 
@@ -527,10 +519,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 			}
 
 			/* Insert it into the queue using new distance data */
-			memcpy(GISTSearchItemDistanceValues(item, nOrderBys),
-				   so->distanceValues, sizeof(double) * nOrderBys);
-			memcpy(GISTSearchItemDistanceNulls(item, nOrderBys),
-				   so->distanceNulls, sizeof(bool) * nOrderBys);
+			memcpy(item->distances, so->distances,
+				   sizeof(item->distances[0]) * nOrderBys);
 
 			pairingheap_add(so->queue, &item->phNode);
 
@@ -595,8 +585,7 @@ getNextNearest(IndexScanDesc scan)
 			scan->xs_recheck = item->data.heap.recheck;
 
 			index_store_float8_orderby_distances(scan, so->orderByTypes,
-												 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-												 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
+												 item->distances,
 												 item->data.heap.recheckDistances);
 
 			/* in an index-only scan, also return the reconstructed tuple. */
@@ -609,10 +598,7 @@ getNextNearest(IndexScanDesc scan)
 			/* visit an index page, extract its items into queue */
 			CHECK_FOR_INTERRUPTS();
 
-			gistScanPage(scan, item,
-						 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-						 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-						 NULL, NULL);
+			gistScanPage(scan, item, item->distances, NULL, NULL);
 		}
 
 		pfree(item);
@@ -650,7 +636,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 
 		fakeItem.blkno = GIST_ROOT_BLKNO;
 		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-		gistScanPage(scan, &fakeItem, NULL, NULL, NULL, NULL);
+		gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
 	}
 
 	if (scan->numberOfOrderBys > 0)
@@ -744,10 +730,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * this page, we fall out of the inner "do" and loop around to
 				 * return them.
 				 */
-				gistScanPage(scan, item,
-							 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-							 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-							 NULL, NULL);
+				gistScanPage(scan, item, item->distances, NULL, NULL);
 
 				pfree(item);
 			} while (so->nPageData == 0);
@@ -778,7 +761,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 	fakeItem.blkno = GIST_ROOT_BLKNO;
 	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
-	gistScanPage(scan, &fakeItem, NULL, NULL, tbm, &ntids);
+	gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
 
 	/*
 	 * While scanning a leaf page, ItemPointers of matching heap tuples will
@@ -793,10 +776,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 		CHECK_FOR_INTERRUPTS();
 
-		gistScanPage(scan, item,
-					 GISTSearchItemDistanceValues(item, scan->numberOfOrderBys),
-					 GISTSearchItemDistanceNulls(item, scan->numberOfOrderBys),
-					 tbm, &ntids);
+		gistScanPage(scan, item, item->distances, tbm, &ntids);
 
 		pfree(item);
 	}
diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c
index e72bf08..323a19d 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -14,6 +14,8 @@
  */
 #include "postgres.h"
 
+#include <math.h>
+
 #include "access/gist_private.h"
 #include "access/gistscan.h"
 #include "access/relscan.h"
@@ -33,26 +35,23 @@ pairingheap_GISTSearchItem_cmp(const pairingheap_node *a, const pairingheap_node
 	const GISTSearchItem *sb = (const GISTSearchItem *) b;
 	IndexScanDesc scan = (IndexScanDesc) arg;
 	int			i;
-	double	   *da = GISTSearchItemDistanceValues(sa, scan->numberOfOrderBys),
-			   *db = GISTSearchItemDistanceValues(sb, scan->numberOfOrderBys);
-	bool	   *na = GISTSearchItemDistanceNulls(sa, scan->numberOfOrderBys),
-			   *nb = GISTSearchItemDistanceNulls(sb, scan->numberOfOrderBys);
 
 	/* Order according to distance comparison */
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (na[i])
+		if (sa->distances[i].isnull)
 		{
-			if (!nb[i])
+			if (!sb->distances[i].isnull)
 				return -1;
 		}
-		else if (nb[i])
+		else if (sb->distances[i].isnull)
 		{
 			return 1;
 		}
 		else
 		{
-			int			cmp = -float8_cmp_internal(da[i], db[i]);
+			int			cmp = -float8_cmp_internal(sa->distances[i].value,
+												   sb->distances[i].value);
 
 			if (cmp != 0)
 				return cmp;
@@ -100,8 +99,7 @@ gistbeginscan(Relation r, int nkeys, int norderbys)
 	so->queueCxt = giststate->scanCxt;	/* see gistrescan */
 
 	/* workspaces with size dependent on numberOfOrderBys: */
-	so->distanceValues = palloc(sizeof(double) * scan->numberOfOrderBys);
-	so->distanceNulls = palloc(sizeof(bool) * scan->numberOfOrderBys);
+	so->distances = palloc(sizeof(so->distances[0]) * scan->numberOfOrderBys);
 	so->qual_ok = true;			/* in case there are zero keys */
 	if (scan->numberOfOrderBys > 0)
 	{
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 2e8f53a..442f414 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -847,14 +847,14 @@ index_getprocinfo(Relation irel,
  */
 void
 index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
-									 double *distanceValues,
-									 bool *distanceNulls, bool recheckOrderBy)
+									 IndexOrderByDistance *distances,
+									 bool recheckOrderBy)
 {
 	int			i;
 
 	scan->xs_recheckorderby = recheckOrderBy;
 
-	if (!distanceValues)
+	if (!distances)
 	{
 		Assert(!scan->xs_recheckorderby);
 
@@ -869,11 +869,11 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 
 	for (i = 0; i < scan->numberOfOrderBys; i++)
 	{
-		if (distanceNulls && distanceNulls[i])
-		{
+		scan->xs_orderbynulls[i] = distances[i].isnull;
+
+		if (scan->xs_orderbynulls[i])
 			scan->xs_orderbyvals[i] = (Datum) 0;
-			scan->xs_orderbynulls[i] = true;
-		}
+
 		if (orderByTypes[i] == FLOAT8OID)
 		{
 #ifndef USE_FLOAT8_BYVAL
@@ -881,8 +881,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float8GetDatum(distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float8GetDatum(distances[i].value);
 		}
 		else if (orderByTypes[i] == FLOAT4OID)
 		{
@@ -892,8 +892,8 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
 			if (!scan->xs_orderbynulls[i])
 				pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
 #endif
-			scan->xs_orderbyvals[i] = Float4GetDatum((float4) distanceValues[i]);
-			scan->xs_orderbynulls[i] = false;
+			if (!scan->xs_orderbynulls[i])
+				scan->xs_orderbyvals[i] = Float4GetDatum((float4) distances[i].value);
 		}
 		else
 		{
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2bd4037..053bf09 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -107,13 +107,13 @@ spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances)
 {
 	/* allocate distance array only for non-NULL items */
 	SpGistSearchItem *item =
-	palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfOrderBys));
+	palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfNonNullOrderBys));
 
 	item->isNull = isnull;
 
-	if (!isnull && so->numberOfOrderBys > 0)
+	if (!isnull && so->numberOfNonNullOrderBys > 0)
 		memcpy(item->distances, distances,
-			   so->numberOfOrderBys * sizeof(double));
+			   sizeof(item->distances[0]) * so->numberOfNonNullOrderBys);
 
 	return item;
 }
@@ -208,6 +208,34 @@ spgPrepareScanKeys(IndexScanDesc scan)
 	so->numberOfOrderBys = scan->numberOfOrderBys;
 	so->orderByData = scan->orderByData;
 
+	if (so->numberOfOrderBys <= 0)
+		so->numberOfNonNullOrderBys = 0;
+	else
+	{
+		int			j = 0;
+
+		/*
+		 * Remove all NULL keys, but remember their offsets in the original
+		 * array.
+		 */
+		for (i = 0; i < scan->numberOfOrderBys; i++)
+		{
+			ScanKey		skey = &so->orderByData[i];
+
+			if (skey->sk_flags & SK_ISNULL)
+				so->nonNullOrderByOffsets[i] = -1;
+			else
+			{
+				if (i != j)
+					so->orderByData[j] = *skey;
+
+				so->nonNullOrderByOffsets[i] = j++;
+			}
+		}
+
+		so->numberOfNonNullOrderBys = j;
+	}
+
 	if (scan->numberOfKeys <= 0)
 	{
 		/* If no quals, whole-index scan is required */
@@ -295,6 +323,8 @@ spgbeginscan(Relation rel, int keysz, int orderbysz)
 		/* This will be filled in spgrescan, but allocate the space here */
 		so->orderByTypes = (Oid *)
 			palloc(sizeof(Oid) * scan->numberOfOrderBys);
+		so->nonNullOrderByOffsets = (int *)
+			palloc(sizeof(int) * scan->numberOfOrderBys);
 
 		/* These arrays have constant contents, so we can fill them now */
 		so->zeroDistances = (double *)
@@ -394,6 +424,7 @@ spgendscan(IndexScanDesc scan)
 	if (scan->numberOfOrderBys > 0)
 	{
 		pfree(so->orderByTypes);
+		pfree(so->nonNullOrderByOffsets);
 		pfree(so->zeroDistances);
 		pfree(so->infDistances);
 		pfree(scan->xs_orderbyvals);
@@ -465,7 +496,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		in.scankeys = so->keyData;
 		in.nkeys = so->numberOfKeys;
 		in.orderbys = so->orderByData;
-		in.norderbys = so->numberOfOrderBys;
+		in.norderbys = so->numberOfNonNullOrderBys;
 		in.reconstructedValue = item->value;
 		in.traversalValue = item->traversalValue;
 		in.level = item->level;
@@ -492,7 +523,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 	if (result)
 	{
 		/* item passes the scankeys */
-		if (so->numberOfOrderBys > 0)
+		if (so->numberOfNonNullOrderBys > 0)
 		{
 			/* the scan is ordered -> add the item to the queue */
 			MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt);
@@ -531,7 +562,7 @@ spgInitInnerConsistentIn(spgInnerConsistentIn *in,
 	in->scankeys = so->keyData;
 	in->orderbys = so->orderByData;
 	in->nkeys = so->numberOfKeys;
-	in->norderbys = so->numberOfOrderBys;
+	in->norderbys = so->numberOfNonNullOrderBys;
 	in->reconstructedValue = item->value;
 	in->traversalMemoryContext = so->traversalCxt;
 	in->traversalValue = item->traversalValue;
@@ -751,7 +782,7 @@ redirect:
 		if (item->isLeaf)
 		{
 			/* We store heap items in the queue only in case of ordered search */
-			Assert(so->numberOfOrderBys > 0);
+			Assert(so->numberOfNonNullOrderBys > 0);
 			storeRes(so, &item->heapPtr, item->value, item->isNull,
 					 item->recheck, item->recheckDistances, item->distances);
 			reportedSome = true;
@@ -874,7 +905,7 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 static void
 storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 			  Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			  double *distances)
+			  double *nonNullDistances)
 {
 	Assert(so->nPtrs < MaxIndexTuplesPerPage);
 	so->heapPtrs[so->nPtrs] = *heapPtr;
@@ -883,13 +914,33 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 
 	if (so->numberOfOrderBys > 0)
 	{
-		if (isnull)
+		if (isnull || so->numberOfNonNullOrderBys <= 0)
 			so->distances[so->nPtrs] = NULL;
 		else
 		{
-			Size		size = sizeof(double) * so->numberOfOrderBys;
+			IndexOrderByDistance *distances =
+				palloc(sizeof(distances[0]) * so->numberOfOrderBys);
+			int			i;
+
+			for (i = 0; i < so->numberOfOrderBys; i++)
+			{
+				int			offset = so->nonNullOrderByOffsets[i];
+
+				if (offset >= 0)
+				{
+					/* Copy non-NULL distance value */
+					distances[i].value = nonNullDistances[offset];
+					distances[i].isnull = false;
+				}
+				else
+				{
+					/* Set distance's NULL flag. */
+					distances[i].value = 0.0;
+					distances[i].isnull = true;
+				}
+			}
 
-			so->distances[so->nPtrs] = memcpy(palloc(size), distances, size);
+			so->distances[so->nPtrs] = distances;
 		}
 	}
 
@@ -929,7 +980,6 @@ spggettuple(IndexScanDesc scan, ScanDirection dir)
 			if (so->numberOfOrderBys > 0)
 				index_store_float8_orderby_distances(scan, so->orderByTypes,
 													 so->distances[so->iPtr],
-													 NULL,
 													 so->recheckDistances[so->iPtr]);
 			so->iPtr++;
 			return true;
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 6c56717..aaa56ae 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -118,6 +118,13 @@ typedef enum IndexUniqueCheck
 } IndexUniqueCheck;
 
 
+/* Nullable ORDER BY distance */
+typedef struct IndexOrderByDistance
+{
+	double		value;
+	bool		isnull;
+} IndexOrderByDistance;
+
 /*
  * generalized index_ interface routines (in indexam.c)
  */
@@ -179,8 +186,7 @@ extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
 								   uint16 procnum);
 extern void index_store_float8_orderby_distances(IndexScanDesc scan,
 												 Oid *orderByTypes,
-												 double *distanceValues,
-												 bool *distanceNulls,
+												 IndexOrderByDistance *distances,
 												 bool recheckOrderBy);
 
 /*
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index ed5b643..ab134c1 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -138,29 +138,15 @@ typedef struct GISTSearchItem
 		GISTSearchHeapItem heap;	/* heap info, if heap tuple */
 	}			data;
 
-	/*
-	 * This data structure is followed by arrays of distance values and
-	 * distance null flags.  Size of both arrays is
-	 * IndexScanDesc->numberOfOrderBys. See macros below for accessing those
-	 * arrays.
-	 */
+	/* numberOfOrderBys entries */
+	IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER];
 } GISTSearchItem;
 
 #define GISTSearchItemIsHeap(item)	((item).blkno == InvalidBlockNumber)
 
-#define SizeOfGISTSearchItem(n_distances) (DOUBLEALIGN(sizeof(GISTSearchItem)) + \
-	(sizeof(double) + sizeof(bool)) * (n_distances))
-
-/*
- * We actually don't need n_distances compute pointer to distance values.
- * Nevertheless take n_distances as argument to have same arguments list for
- * GISTSearchItemDistanceValues() and GISTSearchItemDistanceNulls().
- */
-#define GISTSearchItemDistanceValues(item, n_distances) \
-	((double *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem))))
-
-#define GISTSearchItemDistanceNulls(item, n_distances) \
-	((bool *) ((Pointer) (item) + DOUBLEALIGN(sizeof(GISTSearchItem)) + sizeof(double) * (n_distances)))
+#define SizeOfGISTSearchItem(n_distances) \
+	(offsetof(GISTSearchItem, distances) + \
+	 sizeof(IndexOrderByDistance) * (n_distances))
 
 /*
  * GISTScanOpaqueData: private state for a scan of a GiST index
@@ -176,8 +162,7 @@ typedef struct GISTScanOpaqueData
 	bool		firstCall;		/* true until first gistgettuple call */
 
 	/* pre-allocated workspace arrays */
-	double	   *distanceValues; /* output area for gistindex_keytest */
-	bool	   *distanceNulls;
+	IndexOrderByDistance *distances;	/* output area for gistindex_keytest */
 
 	/* info about killed items if any (killedItems is NULL if never used) */
 	OffsetNumber *killedItems;	/* offset numbers of killed items */
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
index e0d1178..3d322c9 100644
--- a/src/include/access/spgist_private.h
+++ b/src/include/access/spgist_private.h
@@ -169,8 +169,12 @@ typedef struct SpGistScanOpaqueData
 	int			numberOfKeys;	/* number of index qualifier conditions */
 	ScanKey		keyData;		/* array of index qualifier descriptors */
 	int			numberOfOrderBys;	/* number of ordering operators */
+	int			numberOfNonNullOrderBys;	/* number of ordering operators
+											 * with non-NULL arguments */
 	ScanKey		orderByData;	/* array of ordering op descriptors */
 	Oid		   *orderByTypes;	/* array of ordering op return types */
+	int		   *nonNullOrderByOffsets;	/* array of offset of non-NULL ordering
+										 * keys in the original array */
 	Oid			indexCollation; /* collation of index column */
 
 	/* Opclass defined functions: */
@@ -195,7 +199,9 @@ typedef struct SpGistScanOpaqueData
 	bool		recheckDistances[MaxIndexTuplesPerPage];	/* distance recheck
 															 * flags */
 	HeapTuple	reconTups[MaxIndexTuplesPerPage];	/* reconstructed tuples */
-	double	   *distances[MaxIndexTuplesPerPage];	/* distances (for recheck) */
+
+	/* distances (for recheck) */
+	IndexOrderByDistance *distances[MaxIndexTuplesPerPage];
 
 	/*
 	 * Note: using MaxIndexTuplesPerPage above is a bit hokey since
diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out
index 81b4a67..5ccea91 100644
--- a/src/test/regress/expected/create_index_spgist.out
+++ b/src/test/regress/expected/create_index_spgist.out
@@ -555,6 +555,16 @@ WHERE seq.dist IS DISTINCT FROM idx.dist;
 ---+------+---+---+------+---
 (0 rows)
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+      p      
+-------------
+ (59,21)
+ (9853,112)
+ (1239,5647)
+(3 rows)
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
                          QUERY PLAN                         
diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql
index 8e6c453..629936f 100644
--- a/src/test/regress/sql/create_index_spgist.sql
+++ b/src/test/regress/sql/create_index_spgist.sql
@@ -225,6 +225,11 @@ SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
 ON seq.n = idx.n
 WHERE seq.dist IS DISTINCT FROM idx.dist;
 
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
 SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3cdfa8..fb65265 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1043,6 +1043,7 @@ IndexList
 IndexOnlyScan
 IndexOnlyScanState
 IndexOptInfo
+IndexOrderByDistance
 IndexPath
 IndexRuntimeKeyInfo
 IndexScan
-- 
2.7.4


--------------5AE546B531D96C8320FA33AB
Content-Type: text/x-patch;
 name="0002-Use-IndexOrderByDistance-in-SP-GiST-user-functions-v3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0002-Use-IndexOrderByDistance-in-SP-GiST-user-functions-v3.p";
 filename*1="atch"



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH] Make NumericVar storage semantics explicit
@ 2026-05-10 14:12  =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: =?utf-8?B?Q2hlbmh1aU1v?= @ 2026-05-10 14:12 UTC (permalink / raw)
  To: pgsql-hackers

Hi all,


This patch aims to make the memory and ownership semantics of NumericVar
explicit in PostgreSQL, and to reorganize the related helper functions
accordingly, so that the rules for digit-buffer acquisition, release,
borrowing, and writing become clearer, more consistent, and easier
to maintain.


This patch does not change the mathematical algorithms used by numeric
itself. It focuses on the memory model, state transitions, and ownership
semantics of NumericVar and the related helper paths, and adjusts those
paths as needed so that they match the new model.


This patch is also not a performance patch. It does not introduce
aggressive performance optimizations, nor does it attempt to rewrite
all higher-level numeric routines in this round. Instead, its focus is to:


- make previously scattered implicit contracts explicit;
- make the storage / borrow / own semantics of NumericVar easier to
&nbsp; &nbsp; understand and maintain;
- actually connect this new model to the core comparison, hashing,
&nbsp; &nbsp; and basic arithmetic paths;
- verify through regression tests that SQL-visible behavior remains unchanged.


Although this patch is not aimed at improving performance, a clearer
and more explicit memory model can provide a more solid foundation for
future internal numeric optimizations. The related tests also indicate
that this refactoring does not introduce any visible performance regression.


Background and motivation
-------------------------


NumericVar is the in-memory working representation of PostgreSQL's numeric
type. It is not the packed Numeric datum stored in the database itself, but
the working representation used by internal paths such as comparison,
arithmetic, and aggregation.


In the current implementation, there are already several different memory
semantics around NumericVar, for example:


- a variable may be in an "empty" state, with no active digit storage;
- a variable may borrow digits from an external Numeric datum as a read-only view;
- a variable may own a writable digit buffer for internal arithmetic operations;
- in some paths, digit storage comes from a fixed-size inline buffer;
- in some paths, digit storage comes from heap allocation.


These conventions already exist in the code, but they remain largely
implicit rather than being expressed as a single, explicit internal
contract. This makes ownership, mutability, and buffer-transition rules
harder to reason about, especially when switching between packed Numeric
datums and the working representation.


Accordingly, the goal of this patch is not to introduce new numeric
algorithms, but to first make these already-existing but insufficiently
documented memory semantics explicit and consistent.


The new memory model
--------------------


This patch introduces a more explicit NumericVar storage-state model to
describe the meaning of the current digit storage of a NumericVar.


In addition to the existing numeric metadata, NumericVar now has a few
state-related fields, including:


capacity: the physical capacity of the current owned digit buffer;
state: the current storage state of the NumericVar;
borrow_kind: in borrowed state, which kind of storage the current digits
are borrowed from;
inline_buf: whether the variable is associated with a fixed-size inline home.


This patch also introduces BufferedNumericVar, which wraps a NumericVar
together with a fixed-size inline buffer, used for:


inline writable digit storage;
or, when needed, as workspace to hold a detoasted numeric datum.


It is important to emphasize that BufferedNumericVar is not a new SQL-visible
representation. It is only an internal helper structure used by numeric to
support the new memory model.


The new storage states are:


NUMVAR_EMPTY
NUMVAR_BORROWED
NUMVAR_OWNED_INLINE
NUMVAR_OWNED_HEAP


For borrowed state, this patch further distinguishes:


NUMVAR_BORROW_EXTERNAL
NUMVAR_BORROW_INLINE


This makes it possible to distinguish more clearly between:


borrowing an external Numeric datum or static constant representation;
borrowing a detoasted numeric representation currently residing in a
BufferedNumericVar inline buffer.


Core helpers and interface semantics


Under the new model, the related helpers are reorganized into clearer layers:


- raw storage helpers:
&nbsp; &nbsp; only responsible for low-level raw digit-storage allocation;
- storage-state helpers:
&nbsp; &nbsp; responsible for transitions among borrowed / owned / inline / heap states;
- variable-level helpers:
&nbsp; &nbsp; responsible for NumericVar initialization, copying,
&nbsp; &nbsp; buffer acquisition, and release;
- result preparation / finalization helpers:
&nbsp; &nbsp; responsible for alias-safe result preparation and commit
&nbsp; &nbsp; during arithmetic operations.


The purpose of this split is to make the already-existing but previously
scattered rules clearer, not to introduce new SQL-visible behavior.


Core paths already using the new model
--------------------------------------


This patch does not merely introduce unused infrastructure.
The new model is already used in core numeric execution paths.


The SQL-visible core entry points already using the new model include:


cmp_numerics
numeric_hash
numeric_add
numeric_sub
numeric_mul
numeric_div


This means that:


comparison paths already use the explicit state / borrowed / owned semantics;
hashing paths already interact with the new initialization / borrow semantics;
the basic arithmetic paths already interact with the new state model
through the new result-preparation / result-finalization helpers.


The lower-level variable arithmetic bodies such as
add_var/sub_var/mul_var/div_var are not completely
rewritten into a fully new style in this patch, but their
result preparation, alias-safe behavior, and result finalization
are already integrated with the explicit state model through the
new helper framework.


More complex higher-level numeric routines such as sqrt, ln, log,
and power still largely keep their original algorithmic organization
in this patch, and are only adjusted as needed to remain compatible
with the new helper semantics. This is an intentional choice: this
patch focuses first on making the underlying NumericVar contract
explicit and on integrating the new model into core paths, rather
than trying to rewrite all higher-level numeric routines at once.


Testing
-------


To verify that this refactoring does not change SQL-visible behavior,
this patch adds and reorganizes a fairly broad set of regression tests.


The tests cover multiple precision / scale combinations, including:


NUMERIC(9, 2)
NUMERIC(18, 2)
NUMERIC(38, 2)
NUMERIC(80, 16)
NUMERIC(175, 35)
NUMERIC(400, 80)
NUMERIC(1000, 200)
NUMERIC(1000, 800)
NUMERIC(1000, 995)
NUMERIC(1000, 1000)


The test coverage includes:


basic arithmetic operations;
composite expressions;
comparison-sensitive paths;
window functions;
join-driven expressions;
aggregate expressions;
post-update verification;
error cases;
index / index-only related paths.


For excessively long outputs, digest-based verification is used so
that the expected files are not dominated by very large numeric literals.


In addition to the new and adjusted numeric-related tests, the
patch also passes the full regression test suite, confirming that:


the new state model and helper refactoring do not break existing behavior;
higher-level numeric paths still work correctly after being adapted
to the new helper semantics.


I would especially appreciate feedback on:


- whether this storage-state model makes the existing NumericVar
&nbsp; &nbsp; contract clearer;
- whether this helper split is sensible;
- whether the borrowed versus owned terminology is clear enough;
- whether the amount of detail in the updated comments and invariants
&nbsp; &nbsp; is appropriate.


Thanks for reading.


This series is split into two patches:


make NumericVar storage states explicit and connect
the new model to core numeric paths add regression coverage


I would especially appreciate feedback on the state model,
helper split, and terminology.


Regards,
Chenhui Mo

Attachments:

  [application/octet-stream] 0001-numeric-make-NumericVar-storage-states-explicit-and-.patch (56.2K, ../../[email protected]/3-0001-numeric-make-NumericVar-storage-states-explicit-and-.patch)
  download | inline diff:
From c363ac4ce91fa72880d94e96084670e134f98f06 Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:42:17 +0800
Subject: [PATCH 1/2] numeric: make NumericVar storage states explicit and use
 them in core paths

Clarify the in-memory storage model of NumericVar by making buffer
ownership and mutability states explicit, and reorganize the related
helper layer around that model.

This introduces explicit storage states for empty, borrowed,
owned-inline, and owned-heap representations, together with
borrow-kind tracking for borrowed digit views. It also adds
BufferedNumericVar as the inline-home wrapper used by the new model.

The patch then connects the new helper semantics to core comparison,
hashing, and basic arithmetic paths, while keeping higher-level numeric
algorithms structurally close to the existing implementation.

The goal is not to alter SQL-visible numeric behavior, but to make the
existing internal contract around NumericVar storage easier to
understand, document, and maintain.
---
 src/backend/utils/adt/numeric.c | 1197 ++++++++++++++++++++++++-------
 1 file changed, 949 insertions(+), 248 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..f85abf12881 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -104,6 +104,7 @@ typedef int16 NumericDigit;
 #endif
 
 #define NBASE_SQR	(NBASE * NBASE)
+#define NUMERIC_INLINE_BUFFER_SIZE 128
 
 /*
  * The Numeric type as stored on disk.
@@ -261,66 +262,165 @@ struct NumericData
 #define NUMERIC_WEIGHT_MAX			PG_INT16_MAX
 
 /* ----------
- * NumericVar is the format we use for arithmetic.  The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array.  If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight.  Another way to say it is that there are weight+1
- * digits before the decimal point.  It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar.  digits points at the first digit in actual use (the one
- * with the specified weight).  We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space.  We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present.  NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
+ * NumericVar is the in-memory working representation of a numeric value.
+ *
+ * A NumericVar always stores the logical numeric metadata (ndigits, weight,
+ * sign, dscale), but the physical storage backing digits[] is stateful.
+ *
+ * There are four storage states:
+ *
+ * 1. NUMVAR_EMPTY
+ *    The variable carries no digits storage and no digit payload.
+ *
+ * 2. NUMVAR_BORROWED
+ *    digits points to storage owned by some other representation.
+ *    In this state the current NumericVar does not own writable digit
+ *    storage, and buf is NULL.
+ *
+ *    Borrowed storage is further classified by borrow_kind:
+ *
+ *    - NUMVAR_BORROW_EXTERNAL
+ *      digits points into external numeric storage, such as a detoasted
+ *      Numeric datum.  This storage is read-only from the NumericVar's
+ *      perspective and cannot be upgraded in-place to writable inline
+ *      storage.
+ *
+ *    - NUMVAR_BORROW_INLINE
+ *      digits points into numeric storage currently materialized inside the
+ *      inline buffer of an associated BufferedNumericVar.  This is still a
+ *      read-only borrowed view, but because the underlying memory belongs to
+ *      the same buffered home, it may later be converted into writable inline
+ *      working storage.
+ *
+ * 3. NUMVAR_OWNED_INLINE
+ *    buf points to the inline buffer associated with this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * 4. NUMVAR_OWNED_HEAP
+ *    buf points to palloc'd writable storage owned by this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * In owned states, buf is the base of the current writable digit storage and
+ * digits points to the logical first digit.  As in the historical NumericVar
+ * representation, owned storage is initially allocated with a spare leading
+ * digit so that carry propagation can enlarge the value without immediate
+ * reallocation.  Stored numeric datums do not have this extra working space;
+ * it exists only in the in-memory working representation.  Therefore, a
+ * borrowed view of a stored/detoasted Numeric datum must not be treated as a
+ * writable work buffer without an explicit state transition.
+ *
+ * Note, however, that digits is not required to remain equal to buf + 1 for
+ * the lifetime of an owned NumericVar.  Subsequent normalization steps such as
+ * stripping leading zeroes may advance digits within the owned buffer, as long
+ * as the active digit range remains within the storage owned by buf.
+ *
+ * inline_buf, when non-NULL, identifies the fixed-size inline home provided by
+ * BufferedNumericVar.  It does not by itself imply that the variable is
+ * currently in NUMVAR_OWNED_INLINE state.  For example, a NumericVar may be in
+ * NUMVAR_BORROWED or NUMVAR_OWNED_HEAP state while still retaining an inline
+ * home for possible future reuse.
+ *
+ * capacity is the physical capacity of the current owned buffer, including the
+ * spare leading digit.  It is meaningful only in owned states, and is zero in
+ * empty or borrowed states.
+ *
+ * As with the traditional implementation, variable-level functions should be
+ * written so that a NumericVar may safely be used as both input and
+ * destination.  State transitions must preserve that property.
+ *
+ * NumericVar state invariants:
+ *
+ * NUMVAR_EMPTY:
+ *     buf == NULL
+ *     digits == NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *
+ * NUMVAR_BORROWED:
+ *     buf == NULL
+ *     borrow_kind != NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *     digits points to borrowed storage, unless sign is special
+ *
+ * NUMVAR_OWNED_INLINE:
+ *     buf == inline_buf
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == NUMERIC_INLINE_BUFFER_SIZE
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * NUMVAR_OWNED_HEAP:
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity > 0
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * Special values are represented through sign, not through a separate storage
+ * state.  For NaN and infinities, only sign is semantically meaningful.
  * ----------
  */
+
+typedef enum NUMERIC_VAR_STATUS
+{
+	NUMVAR_EMPTY,
+	NUMVAR_BORROWED,
+	NUMVAR_OWNED_INLINE,
+	NUMVAR_OWNED_HEAP
+}			NUMERIC_VAR_STATUS;
+
+typedef enum NUMERIC_BORROW_KIND
+{
+	NUMVAR_BORROW_NONE,
+	NUMVAR_BORROW_EXTERNAL,
+	NUMVAR_BORROW_INLINE
+}			NUMERIC_BORROW_KIND;
+
 typedef struct NumericVar
 {
-	int			ndigits;		/* # of digits in digits[] - can be 0! */
+	/* logical numeric metadata */
+	int			ndigits;		/* # of digits in digits[]; may be 0 */
 	int			weight;			/* weight of first digit */
 	int			sign;			/* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
 	int			dscale;			/* display scale */
-	NumericDigit *buf;			/* start of palloc'd space for digits[] */
-	NumericDigit *digits;		/* base-NBASE digits */
+
+	/* storage state metadata */
+	NUMERIC_VAR_STATUS state;	/* current storage state */
+	NUMERIC_BORROW_KIND borrow_kind;	/* source of borrowed digits, if
+										 * borrowed */
+	int			capacity;		/* current owned buffer capacity, spare digit
+								 * included */
+
+	/* current digit storage */
+	NumericDigit *buf;			/* base of current owned writable storage, or
+								 * NULL */
+	NumericDigit *digits;		/* logical first base-NBASE digit, or NULL */
+
+	/* optional fixed-size inline home */
+	NumericDigit *inline_buf;	/* inline buffer of associated
+								 * BufferedNumericVar, or NULL */
 } NumericVar;
 
+#define NUMERIC_VARLENA_SIZE (VARHDRSZ / sizeof(NumericDigit) + \
+	(sizeof(uint16) + sizeof(int16)) / sizeof(NumericDigit) + \
+	NUMERIC_INLINE_BUFFER_SIZE + DIV_GUARD_DIGITS + 4)
+
+typedef struct BufferedNumericVar
+{
+	NumericVar	var;
+	int64		magic;			/* used for align with 8-byte */
+	NumericDigit buffer[NUMERIC_VARLENA_SIZE];
+}			BufferedNumericVar;
+
+
+#define BUFFERED_NUMERIC_VAR_MAGIC 0x4E554D455249435F	/* 'NUMERIC_MAGIC' */
+
+#define NUMERICVAR_IS_SPECIAL(v) \
+    ((v)->sign == NUMERIC_NAN || (v)->sign == NUMERIC_PINF || (v)->sign == NUMERIC_NINF)
+
 
 /* ----------
  * Data for generate_series
@@ -415,18 +515,18 @@ typedef struct NumericSumAccum
  */
 static const NumericDigit const_zero_data[1] = {0};
 static const NumericVar const_zero =
-{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data};
+{0, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_data, NULL};
 
 static const NumericDigit const_one_data[1] = {1};
 static const NumericVar const_one =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericVar const_minus_one =
-{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_NEG, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericDigit const_two_data[1] = {2};
 static const NumericVar const_two =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_two_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_zero_point_nine_data[1] = {9000};
@@ -436,7 +536,7 @@ static const NumericDigit const_zero_point_nine_data[1] = {90};
 static const NumericDigit const_zero_point_nine_data[1] = {9};
 #endif
 static const NumericVar const_zero_point_nine =
-{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data};
+{1, -1, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_point_nine_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_one_point_one_data[2] = {1, 1000};
@@ -446,28 +546,31 @@ static const NumericDigit const_one_point_one_data[2] = {1, 10};
 static const NumericDigit const_one_point_one_data[2] = {1, 1};
 #endif
 static const NumericVar const_one_point_one =
-{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data};
+{2, 0, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_point_one_data, NULL};
 
 static const NumericVar const_nan =
-{0, 0, NUMERIC_NAN, 0, NULL, NULL};
+{0, 0, NUMERIC_NAN, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_pinf =
-{0, 0, NUMERIC_PINF, 0, NULL, NULL};
+{0, 0, NUMERIC_PINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_ninf =
-{0, 0, NUMERIC_NINF, 0, NULL, NULL};
+{0, 0, NUMERIC_NINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 #if DEC_DIGITS == 4
 static const int round_powers[4] = {0, 1000, 100, 10};
 #endif
 
-
 /* ----------
  * Local functions
  * ----------
  */
 
 #ifdef NUMERIC_DEBUG
+static inline int check_digit_overflow(const NumericVar *var);
+static inline void check_numericvar_state(const NumericVar *var, const char *where);
+#define CHECK_NUMERICVAR(v) check_numericvar_state((v), __func__)
+#define CHECK_NUMERICVAR_AT(v, where) check_numericvar_state((v), (where))
 static void dump_numeric(const char *str, Numeric num);
 static void dump_var(const char *str, NumericVar *var);
 #else
@@ -475,15 +578,65 @@ static void dump_var(const char *str, NumericVar *var);
 #define dump_var(s,v)
 #endif
 
-#define digitbuf_alloc(ndigits)  \
-	((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit)))
-#define digitbuf_free(buf)	\
-	do { \
-		 if ((buf) != NULL) \
-			 pfree(buf); \
-	} while (0)
+/*
+ * digitbuf_alloc_raw() -
+ *
+ * Allocate raw heap-backed digit storage with the specified physical
+ * capacity.
+ *
+ * The capacity is measured in NumericDigit units and includes any spare
+ * leading digit space required by the caller.
+ *
+ * This is a low-level storage primitive.  It does not inspect or update any
+ * NumericVar state, and it does not by itself establish a complete working
+ * NumericVar representation.
+ */
+static inline NumericDigit *
+digitbuf_alloc_raw(int sz)
+{
+	return (NumericDigit *) palloc(sizeof(NumericDigit) * sz);
+}
 
-#define init_var(v)		memset(v, 0, sizeof(NumericVar))
+/*
+ * init_var() -
+ *
+ * Initialize a NumericVar in NUMVAR_EMPTY storage state, with no active digit
+ * storage, no borrowed digit view, and no current digit payload.
+ *
+ * This establishes a clean starting point for later transitions into
+ * borrowed or owned states.
+ */
+static inline void
+init_var(NumericVar *v)
+{
+	memset(v, 0, sizeof(NumericVar));
+}
+
+/*
+ * init_buffered_var() -
+ *
+ * Initialize a BufferedNumericVar so that its embedded NumericVar starts with
+ * writable inline working storage backed by the object's fixed-size inline
+ * buffer.
+ *
+ * The inline buffer is installed as the current owned storage, and the digit
+ * pointer is initialized to the normal working position after the spare
+ * leading digit.
+ */
+static void
+init_buffered_var(BufferedNumericVar * bvar)
+{
+	bvar->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&bvar->var);
+
+	bvar->var.inline_buf = bvar->buffer;
+	bvar->var.capacity = NUMERIC_INLINE_BUFFER_SIZE;
+	bvar->var.state = NUMVAR_OWNED_INLINE;
+	bvar->var.borrow_kind = NUMVAR_BORROW_NONE;
+	bvar->var.buf = bvar->buffer;
+	bvar->var.buf[0] = 0;
+	bvar->var.digits = bvar->buffer + 1;
+}
 
 #define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
 	(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
@@ -494,8 +647,27 @@ static void dump_var(const char *str, NumericVar *var);
 	(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
 	(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
 
+static void digitbuf_free(NumericVar *var);
+
+static NumericDigit *digitbuf_get(NumericVar *var,
+								  int require_ndigits);
+
 static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
+
+static bool result_aliases_input(const NumericVar *result,
+								 const NumericVar *a,
+								 const NumericVar *b);
+
+static NumericVar *prepare_result_var(NumericVar *result,
+									  const NumericVar *a,
+									  const NumericVar *b,
+									  int res_ndigits,
+									  BufferedNumericVar * tmp);
+
+static void finish_result_var(NumericVar *work_result,
+							  NumericVar *result);
+
 static void zero_var(NumericVar *var);
 
 static bool set_var_from_str(const char *str, const char *cp,
@@ -507,7 +679,10 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
 												 const char **endptr,
 												 Node *escontext);
 static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_buffered_var_from_num(Numeric src, BufferedNumericVar * bvar);
+static varlena *numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+											   size_t buffer_size);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
 static char *get_str_from_var(const NumericVar *var);
 static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -543,6 +718,7 @@ static int	cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
 						   int var1weight, int var1sign,
 						   const NumericDigit *var2digits, int var2ndigits,
 						   int var2weight, int var2sign);
+
 static void add_var(const NumericVar *var1, const NumericVar *var2,
 					NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -2416,8 +2592,8 @@ numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
 Datum
 numeric_cmp(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	int			result;
 
 	result = cmp_numerics(num1, num2);
@@ -2428,12 +2604,11 @@ numeric_cmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
-
 Datum
 numeric_eq(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) == 0;
@@ -2447,8 +2622,8 @@ numeric_eq(PG_FUNCTION_ARGS)
 Datum
 numeric_ne(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) != 0;
@@ -2462,8 +2637,8 @@ numeric_ne(PG_FUNCTION_ARGS)
 Datum
 numeric_gt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) > 0;
@@ -2477,8 +2652,8 @@ numeric_gt(PG_FUNCTION_ARGS)
 Datum
 numeric_ge(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) >= 0;
@@ -2492,8 +2667,8 @@ numeric_ge(PG_FUNCTION_ARGS)
 Datum
 numeric_lt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) < 0;
@@ -2507,8 +2682,8 @@ numeric_lt(PG_FUNCTION_ARGS)
 Datum
 numeric_le(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) <= 0;
@@ -2523,6 +2698,11 @@ static int
 cmp_numerics(Numeric num1, Numeric num2)
 {
 	int			result;
+	BufferedNumericVar var1;
+	BufferedNumericVar var2;
+
+	num1 = init_buffered_var_from_num(num1, &var1);
+	num2 = init_buffered_var_from_num(num2, &var2);
 
 	/*
 	 * We consider all NANs to be equal and larger than any non-NAN (including
@@ -2714,7 +2894,7 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
 Datum
 hash_numeric(PG_FUNCTION_ARGS)
 {
-	Numeric		key = PG_GETARG_NUMERIC(0);
+	Numeric		key = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
 	Datum		digit_hash;
 	Datum		result;
 	int			weight;
@@ -2723,6 +2903,9 @@ hash_numeric(PG_FUNCTION_ARGS)
 	int			i;
 	int			hash_len;
 	NumericDigit *digits;
+	BufferedNumericVar var;
+
+	key = init_buffered_var_from_num(key, &var);
 
 	/* If it's NaN or infinity, don't try to hash the rest of the fields */
 	if (NUMERIC_IS_SPECIAL(key))
@@ -2865,12 +3048,16 @@ hash_numeric_extended(PG_FUNCTION_ARGS)
 Datum
 numeric_add(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_add_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2882,11 +3069,15 @@ numeric_add(PG_FUNCTION_ARGS)
 Numeric
 numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+
+	BufferedNumericVar result;
 	Numeric		res;
 
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2915,18 +3106,15 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_ninf);
 	}
 
-	/*
-	 * Unpack the values, let add_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
 
-	init_var(&result);
-	add_var(&arg1, &arg2, &result);
+	add_var(&arg1.var, &arg2.var, &result.var);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -2940,12 +3128,16 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_sub(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_sub_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2958,11 +3150,17 @@ numeric_sub(PG_FUNCTION_ARGS)
 Numeric
 numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let sub_var() compute the result and return it.
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2991,18 +3189,14 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_pinf);
 	}
 
-	/*
-	 * Unpack the values, let sub_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	sub_var(&arg1.var, &arg2.var, &result.var);
 
-	init_var(&result);
-	sub_var(&arg1, &arg2, &result);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3016,12 +3210,16 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_mul(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_mul_safe(num1, num2, fcinfo->context);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
 		PG_RETURN_NULL();
 
@@ -3037,11 +3235,25 @@ numeric_mul(PG_FUNCTION_ARGS)
 Numeric
 numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let mul_var() compute the result and return it.
+	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
+	 * case of numeric_mul(), which is invoked for the * operator on numerics,
+	 * we request exact representation for the product (rscale = sum(dscale of
+	 * arg1, dscale of arg2)).  If the exact result has more digits after the
+	 * decimal point than can be stored in a numeric, we round it.  Rounding
+	 * after computing the exact result ensures that the final result is
+	 * correctly rounded (rounding in mul_var() using a truncated product
+	 * would not guarantee this).
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3102,29 +3314,17 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 		Assert(false);
 	}
 
-	/*
-	 * Unpack the values, let mul_var() compute the result and return it.
-	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
-	 * case of numeric_mul(), which is invoked for the * operator on numerics,
-	 * we request exact representation for the product (rscale = sum(dscale of
-	 * arg1, dscale of arg2)).  If the exact result has more digits after the
-	 * decimal point than can be stored in a numeric, we round it.  Rounding
-	 * after computing the exact result ensures that the final result is
-	 * correctly rounded (rounding in mul_var() using a truncated product
-	 * would not guarantee this).
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	mul_var(&arg1.var, &arg2.var, &result.var, arg1.var.dscale + arg2.var.dscale);
 
-	init_var(&result);
-	mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
+	if (result.var.dscale > NUMERIC_DSCALE_MAX)
+		round_var(&result.var, NUMERIC_DSCALE_MAX);
 
-	if (result.dscale > NUMERIC_DSCALE_MAX)
-		round_var(&result, NUMERIC_DSCALE_MAX);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3138,12 +3338,16 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_div(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_div_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -3156,12 +3360,18 @@ numeric_div(PG_FUNCTION_ARGS)
 Numeric
 numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 	int			rscale;
 
+	/*
+	 * Unpack the arguments
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3209,31 +3419,27 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_zero);
 	}
 
-	/*
-	 * Unpack the arguments
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
-
-	init_var(&result);
+	init_buffered_var(&result);
 
 	/*
 	 * Select scale for division result
 	 */
-	rscale = select_div_scale(&arg1, &arg2);
+	rscale = select_div_scale(&arg1.var, &arg2.var);
 
 	/* Check for division by zero */
-	if (arg2.ndigits == 0 || arg2.digits[0] == 0)
+	if (arg2.var.ndigits == 0 || arg2.var.digits[0] == 0)
 		goto division_by_zero;
 
 	/*
 	 * Do the divide and return the result
 	 */
-	div_var(&arg1, &arg2, &result, rscale, true, true);
+	div_var(&arg1.var, &arg2.var, &result.var, rscale, true, true);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 
@@ -3450,17 +3656,17 @@ numeric_inc(PG_FUNCTION_ARGS)
 Datum
 numeric_smaller(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) < 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -3472,17 +3678,17 @@ numeric_smaller(PG_FUNCTION_ARGS)
 Datum
 numeric_larger(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) > 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -4854,7 +5060,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	state->N++;
 
 	/* Accumulate sums */
@@ -4862,7 +5067,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	if (state->calcSumX2)
 		accum_sum_add(&(state->sumX2), &X2);
-
 	MemoryContextSwitchTo(old_context);
 }
 
@@ -4932,7 +5136,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 			return false;
 		}
 	}
-
 	/* if we need X^2, calculate that in short-lived context */
 	if (state->calcSumX2)
 	{
@@ -4942,7 +5145,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	if (state->N-- > 1)
 	{
 		/* Negate X, to subtract it from the sum */
@@ -4960,14 +5162,11 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 	{
 		/* Zero the sums */
 		Assert(state->N == 0);
-
 		accum_sum_reset(&state->sumX);
 		if (state->calcSumX2)
 			accum_sum_reset(&state->sumX2);
 	}
-
 	MemoryContextSwitchTo(old_context);
-
 	return true;
 }
 
@@ -6689,6 +6888,80 @@ dump_var(const char *str, NumericVar *var)
 
 	printf("\n");
 }
+
+
+static inline int
+check_digit_overflow(const NumericVar *var)
+{
+	int			i;
+
+	for (i = 0; i < var->ndigits; i++)
+	{
+		if (var->digits[i] >= NBASE)
+		{
+			return 1;
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * check_numericvar_state() -
+ *
+ * Validate internal NumericVar storage-state invariants.
+ *
+ * This is primarily intended as a debugging aid for future changes to the
+ * NumericVar storage-state machinery.
+ */
+static inline void
+check_numericvar_state(const NumericVar *var, const char *where)
+{
+	Assert(var != NULL);
+	Assert(var->ndigits >= 0);
+
+	if (var->state != NUMVAR_BORROWED)
+		Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->buf == NULL);
+			Assert(var->digits == NULL);
+			Assert(var->capacity == 0);
+			break;
+
+		case NUMVAR_BORROWED:
+			Assert(var->buf == NULL);
+			Assert(var->capacity == 0);
+			Assert(var->borrow_kind != NUMVAR_BORROW_NONE);
+
+			if (!NUMERICVAR_IS_SPECIAL(var))
+				Assert(var->digits != NULL);
+			break;
+
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->inline_buf != NULL);
+			Assert(var->buf == var->inline_buf);
+			Assert(var->digits != NULL);
+			Assert(var->capacity == NUMERIC_INLINE_BUFFER_SIZE);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->buf != NULL);
+			Assert(var->digits != NULL);
+			Assert(var->capacity > 0);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		default:
+			elog(ERROR, "unexpected NumericVar state %d at %s",
+				 (int) var->state, where);
+	}
+}
 #endif							/* NUMERIC_DEBUG */
 
 
@@ -6703,35 +6976,296 @@ dump_var(const char *str, NumericVar *var)
  * ----------------------------------------------------------------------
  */
 
+/*
+ * digitbuf_get() -
+ *
+ * Ensure that a NumericVar has owned writable digit storage with at least the
+ * requested physical capacity, including any spare leading digit required by
+ * the caller.
+ *
+ * Regardless of the variable's current state, this function returns the base
+ * of writable owned storage and leaves the variable in either
+ * NUMVAR_OWNED_INLINE or NUMVAR_OWNED_HEAP state.
+ *
+ * This function is concerned only with storage acquisition and state
+ * transition.  Callers remain responsible for maintaining the logical numeric
+ * fields and for copying digit contents when required.
+ */
+static inline NumericDigit *
+digitbuf_get(NumericVar *var, int required_capacity)
+{
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf)
+			{
+				var->buf = var->inline_buf;
+				var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+				var->state = NUMVAR_OWNED_INLINE;
+			}
+			else
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_BORROWED:
+			if (var->borrow_kind == NUMVAR_BORROW_EXTERNAL)
+			{
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf != NULL)
+				{
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			else
+			{
+				Assert(var->inline_buf != NULL);
+				Assert(var->borrow_kind == NUMVAR_BORROW_INLINE);
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE)
+				{
+					/*
+					 * NUMVAR_BORROW_INLINE is currently used only for the
+					 * special case where the inline buffer of a
+					 * BufferedNumericVar holds a detoasted Numeric datum.
+					 *
+					 * In this case, the caller guarantees that the borrowed
+					 * buffered representation does not need to be preserved
+					 * as an independent read-only view. Therefore, when the
+					 * requested capacity fits in the inline home, we may
+					 * convert that storage to NUMVAR_OWNED_INLINE by
+					 * resetting buf to the beginning of the inline buffer and
+					 * reestablishing the normal NumericVar working-buffer
+					 * layout there.
+					 */
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			break;
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			Assert(var->inline_buf != NULL);
+			if (required_capacity > var->capacity)
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity > var->capacity)
+			{
+				pfree(var->buf);
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+			}
+			break;
+		default:
+			Assert(false);
+			return NULL;		/* silence compiler warning */
+	}
+
+	var->borrow_kind = NUMVAR_BORROW_NONE;
+	var->digits = var->buf + 1;
+
+	return var->buf;
+}
+
+
+/*
+ * digitbuf_free() -
+ *
+ * Release the current active digit storage or borrowed digit view of a
+ * NumericVar and return it to NUMVAR_EMPTY storage state.
+ *
+ * Heap-backed owned storage is pfree'd.  Inline owned storage is simply
+ * detached as the active buffer and remains available as the variable's
+ * inline home.  Borrowed storage is not freed, but any active borrowed view
+ * is dropped.
+ *
+ * This function resets only storage-state fields.  It does not by itself
+ * establish a meaningful numeric value.
+ */
+static inline void
+digitbuf_free(NumericVar *var)
+{
+	switch (var->state)
+	{
+		case NUMVAR_OWNED_HEAP:
+			pfree(var->buf);
+			pg_fallthrough;
+		case NUMVAR_EMPTY:
+		case NUMVAR_BORROWED:
+		case NUMVAR_OWNED_INLINE:
+			var->buf = NULL;
+			var->capacity = 0;
+			var->digits = NULL;
+			var->state = NUMVAR_EMPTY;
+			var->borrow_kind = NUMVAR_BORROW_NONE;
+			break;
+		default:
+			Assert(false);
+	}
+}
+
+
+/*
+ * result_aliases_input() -
+ *
+ * Return true if the destination variable cannot be used directly as the
+ * output of an arithmetic operation because it aliases one of the inputs.
+ *
+ * In addition to direct pointer equality, this treats result as aliasing if
+ * its current digit pointer falls within the active digit range of either
+ * input variable.
+ */
+static bool
+result_aliases_input(const NumericVar *result,
+					 const NumericVar *a,
+					 const NumericVar *b)
+{
+	if (result == a || result == b)
+		return true;
+
+	/*
+	 * Be conservative: if result currently points into either input digit
+	 * range, treat it as aliasing as well.
+	 */
+	if (result->digits != NULL)
+	{
+		if (a->digits != NULL &&
+			result->digits >= a->digits &&
+			result->digits < a->digits + a->ndigits)
+			return true;
+
+		if (b->digits != NULL &&
+			result->digits >= b->digits &&
+			result->digits < b->digits + b->ndigits)
+			return true;
+	}
+
+	return false;
+}
+
+
+/*
+ * prepare_result_var() -
+ *
+ * Prepare a writable result variable for an arithmetic operation.
+ *
+ * If the caller's destination does not alias either input, writable storage
+ * is allocated directly in that destination and the destination is returned.
+ *
+ * If the destination aliases an input, a temporary BufferedNumericVar is
+ * initialized and allocated instead, so that the operation may read its
+ * inputs without destroying their digit storage.
+ */
+static NumericVar *
+prepare_result_var(NumericVar *result,
+				   const NumericVar *a,
+				   const NumericVar *b,
+				   int res_ndigits,
+				   BufferedNumericVar * tmp)
+{
+	if (result_aliases_input(result, a, b))
+	{
+		init_buffered_var(tmp);
+
+		alloc_var(&tmp->var, res_ndigits);
+
+		return &tmp->var;
+	}
+
+	alloc_var(result, res_ndigits);
+
+	return result;
+}
+
+
+/*
+ * finish_result_var() -
+ *
+ * Finalize a result prepared by prepare_result_var().
+ *
+ * If the arithmetic result was computed directly in the caller's destination,
+ * this function does nothing.
+ *
+ * If the result was computed in a temporary variable, copy that value back
+ * into the caller's destination and release the temporary storage.
+ */
+static void
+finish_result_var(NumericVar *work_result, NumericVar *result)
+{
+	if (work_result != result)
+	{
+		set_var_from_var(work_result, result);
+		free_var(work_result);
+	}
+}
+
 
 /*
  * alloc_var() -
  *
- *	Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
+ * Allocate owned writable digit storage for a NumericVar large enough for the
+ * requested number of logical digits plus one spare leading digit.
+ *
+ * The variable is left in an owned writable state with basic working metadata
+ * initialized, but callers remain responsible for constructing the final
+ * numeric value.
  */
 static void
 alloc_var(NumericVar *var, int ndigits)
 {
-	digitbuf_free(var->buf);
-	var->buf = digitbuf_alloc(ndigits + 1);
-	var->buf[0] = 0;			/* spare digit for rounding */
-	var->digits = var->buf + 1;
+	digitbuf_free(var);
+
+	digitbuf_get(var, ndigits + 1);
+
 	var->ndigits = ndigits;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
+	var->buf[0] = 0;			/* ensure spare digit is zeroed */
 }
 
 
 /*
  * free_var() -
  *
- *	Return the digit buffer of a variable to the free pool
+ * Release the current active storage of a NumericVar and reset its numeric
+ * metadata to an empty working state.
+ *
+ * Unlike digitbuf_free(), this is a variable-level cleanup helper: it clears
+ * both storage-state and value-level fields.
  */
 static void
 free_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
-	var->buf = NULL;
-	var->digits = NULL;
-	var->sign = NUMERIC_NAN;
+	/* Release current active storage, if any. */
+	digitbuf_free(var);
+
+	var->ndigits = 0;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
 }
 
 
@@ -6744,7 +7278,7 @@ free_var(NumericVar *var)
 static void
 zero_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
+	free_var(var);
 	var->buf = NULL;
 	var->digits = NULL;
 	var->ndigits = 0;
@@ -6923,9 +7457,11 @@ set_var_from_str(const char *str, const char *cp,
 	ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
 	dest->sign = sign;
 	dest->weight = weight;
 	dest->dscale = dscale;
+	dest->digits = dest->buf + 1;
 
 	i = DEC_DIGITS - offset;
 	digits = dest->digits;
@@ -7184,6 +7720,8 @@ set_var_from_num(Numeric num, NumericVar *dest)
 	ndigits = NUMERIC_NDIGITS(num);
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
+	dest->digits = dest->buf + 1;
 
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
@@ -7196,50 +7734,187 @@ set_var_from_num(Numeric num, NumericVar *dest)
 /*
  * init_var_from_num() -
  *
- *	Initialize a variable from packed db format. The digits array is not
- *	copied, which saves some cycles when the resulting var is not modified.
- *	Also, there's no need to call free_var(), as long as you don't assign any
- *	other value to it (with set_var_* functions, or by using the var as the
- *	destination of a function like add_var())
+ * Initialize a NumericVar from an input Numeric datum, detoasting the datum
+ * first if necessary.
  *
- *	CAUTION: Do not modify the digits buffer of a var initialized with this
- *	function, e.g by calling round_var() or trunc_var(), as the changes will
- *	propagate to the original Numeric! It's OK to use it as the destination
- *	argument of one of the calculational functions, though.
+ * For finite values, the resulting NumericVar is a borrowed read-only view of
+ * externally owned digit storage, with borrow kind
+ * NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the detoasted representation actually used
+ * to initialize the borrowed view.
  */
-static void
+static Numeric
 init_var_from_num(Numeric num, NumericVar *dest)
 {
+	/* ensure the numeric is always detoasted */
+	num = (Numeric) PG_DETOAST_DATUM(NumericGetDatum(num));
+
+	init_var(dest);
+
 	dest->ndigits = NUMERIC_NDIGITS(num);
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
 	dest->dscale = NUMERIC_DSCALE(num);
-	dest->digits = NUMERIC_DIGITS(num);
-	dest->buf = NULL;			/* digits array is not palloc'd */
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->state = NUMVAR_EMPTY;
+		dest->borrow_kind = NUMVAR_BORROW_NONE;
+	}
+	else
+	{
+		dest->state = NUMVAR_BORROWED;
+		dest->borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+		dest->digits = NUMERIC_DIGITS(num);
+	}
+
+	return num;
+}
+
+
+/*
+ * init_buffered_var_from_num() -
+ *
+ * Initialize a BufferedNumericVar from an input Numeric datum, using the
+ * object's inline buffer as detoast workspace when possible.
+ *
+ * For finite values, the resulting NumericVar is a borrowed read-only view.
+ * If the detoasted representation is materialized in the inline buffer, the
+ * borrow kind is recorded as NUMVAR_BORROW_INLINE; otherwise it is recorded
+ * as NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the representation actually used to
+ * initialize the borrowed view.
+ */
+static Numeric
+init_buffered_var_from_num(Numeric num, BufferedNumericVar * dest)
+{
+	num = (Numeric) numeric_detoast_attr_to_buffer((varlena *) (num),
+												   (unsigned char *) dest->buffer,
+												   sizeof(dest->buffer));
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+		init_var(&dest->var);
+		dest->var.inline_buf = dest->buffer;
+		dest->var.sign = NUMERIC_SIGN(num);
+		return num;
+	}
+
+	dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&dest->var);
+	dest->var.inline_buf = dest->buffer;
+
+	dest->var.state = NUMVAR_BORROWED;
+	dest->var.ndigits = NUMERIC_NDIGITS(num);
+	dest->var.weight = NUMERIC_WEIGHT(num);
+	dest->var.sign = NUMERIC_SIGN(num);
+	dest->var.dscale = NUMERIC_DSCALE(num);
+	dest->var.digits = NUMERIC_DIGITS(num);
+
+	if ((unsigned char *) num == (unsigned char *) dest->buffer)
+		dest->var.borrow_kind = NUMVAR_BORROW_INLINE;
+	else
+		dest->var.borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+	return num;
+}
+
+
+/*
+ * numeric_detoast_attr_to_buffer() -
+ *
+ * Convert a varlena datum to a detoasted representation, preferably using a
+ * caller-provided buffer when that is sufficient.
+ *
+ * This helper is intended for numeric paths.  If the input datum is in
+ * short-header form, it is expanded to ordinary 4-byte-header form, using
+ * the supplied buffer when possible and falling back to palloc'd storage
+ * otherwise.
+ *
+ * If the input datum is not in short-header form, this helper falls back to
+ * detoast_attr().
+ *
+ * The returned pointer may therefore refer either to the caller-provided
+ * buffer or to freshly allocated storage.
+ */
+static varlena *
+numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+							   size_t buffer_size)
+{
+	Size		data_size;
+	Size		new_size;
+	varlena    *new_attr;
+
+	if (!VARATT_IS_SHORT(attr))
+		return pg_detoast_datum(attr);
+
+	/*
+	 * This is a short-header varlena --- convert to 4-byte header format
+	 */
+	data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
+	new_size = data_size + VARHDRSZ;
+
+	if (unlikely(buffer_size < new_size))
+		new_attr = (varlena *) palloc(new_size);
+	else
+		new_attr = (varlena *) buffer;
+	SET_VARSIZE(new_attr, new_size);
+	memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
+	attr = new_attr;
+
+	return attr;
 }
 
 
 /*
  * set_var_from_var() -
  *
- *	Copy one variable into another
+ * Copy one NumericVar into another.
+ *
+ * The destination receives the same numeric value as the source, but this
+ * function always constructs a destination representation that is valid for
+ * the destination's own storage state.  It does not steal storage from the
+ * source.
+ *
+ * This function must remain safe when source and destination are the same
+ * variable.
  */
 static void
 set_var_from_var(const NumericVar *value, NumericVar *dest)
 {
-	NumericDigit *newbuf;
+	if (value == dest)
+		return;
 
-	newbuf = digitbuf_alloc(value->ndigits + 1);
-	newbuf[0] = 0;				/* spare digit for rounding */
-	if (value->ndigits > 0)		/* else value->digits might be null */
-		memcpy(newbuf + 1, value->digits,
-			   value->ndigits * sizeof(NumericDigit));
+	if (NUMERICVAR_IS_SPECIAL(value))
+	{
+		free_var(dest);
+		dest->sign = value->sign;
+
+		return;
+	}
 
-	digitbuf_free(dest->buf);
+	/* ndigits is set in alloc_var */
+	alloc_var(dest, value->ndigits);
 
-	memmove(dest, value, sizeof(NumericVar));
-	dest->buf = newbuf;
-	dest->digits = newbuf + 1;
+	dest->weight = value->weight;
+	dest->dscale = value->dscale;
+	dest->sign = value->sign;
+
+	memcpy(dest->digits,
+		   value->digits,
+		   value->ndigits * sizeof(NumericDigit));
 }
 
 
@@ -8600,6 +9275,7 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
 	 */
 	result->weight = res_weight;
 	result->sign = res_sign;
+	result->ndigits = res_ndigits;
 
 	/* Round to target rscale (and set result->dscale) */
 	round_var(result, rscale);
@@ -8631,6 +9307,8 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	NumericDigit *res_digits;
 	uint32		carry = 0;
 	uint32		term;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Check preconditions */
 	Assert(var1ndigits >= 1);
@@ -8651,8 +9329,9 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	res_weight = var1->weight + var2->weight + 1;
 	res_ndigits = var1ndigits + var2ndigits;
 
-	/* Allocate result digit array */
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -8872,16 +9551,17 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	}
 
 	/* Store the product in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
-	result->dscale = var1->dscale + var2->dscale;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = var1->dscale + var2->dscale;
 
 	/* Strip leading and trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -9457,6 +10137,8 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint32		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9490,7 +10172,9 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9532,12 +10216,14 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -9573,6 +10259,8 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint64		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9606,7 +10294,9 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9648,12 +10338,13 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -11494,6 +12185,8 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			carry = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11514,7 +12207,9 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11543,15 +12238,15 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(carry == 0);			/* else we failed to allow for carry out */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -11579,6 +12274,8 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			borrow = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11599,7 +12296,9 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11628,15 +12327,15 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(borrow == 0);		/* else caller gave us var1 < var2 */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 /*
@@ -12099,7 +12798,9 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 
 	/* Create NumericVars representing the positive and negative sums */
 	init_var(&pos_var);
+	alloc_var(&pos_var, accum->ndigits);
 	init_var(&neg_var);
+	alloc_var(&neg_var, accum->ndigits);
 
 	pos_var.ndigits = neg_var.ndigits = accum->ndigits;
 	pos_var.weight = neg_var.weight = accum->weight;
@@ -12107,8 +12808,8 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 	pos_var.sign = NUMERIC_POS;
 	neg_var.sign = NUMERIC_NEG;
 
-	pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits);
-	neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits);
+	pos_var.buf = pos_var.digits = digitbuf_alloc_raw(accum->ndigits);
+	neg_var.buf = neg_var.digits = digitbuf_alloc_raw(accum->ndigits);
 
 	for (i = 0; i < accum->ndigits; i++)
 	{
-- 
2.43.0



  [application/octet-stream] 0002-numeric-add-regression-coverage-for-NumericVar-state.patch (132.3K, ../../[email protected]/4-0002-numeric-add-regression-coverage-for-NumericVar-state.patch)
  download | inline diff:
From c0a30a385671504e5a7751b373ac3dd234d2d105 Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:43:50 +0800
Subject: [PATCH 2/2] numeric: add regression coverage for NumericVar state
 changes

Add regression coverage for the NumericVar storage-state refactoring.

The tests cover a wide range of numeric typmods, including high-precision
and high-scale cases, and exercise arithmetic, composite expressions,
comparison-sensitive paths, window functions, join-driven expressions,
aggregates, updates, error cases, and index/index-only related paths.

This is intended to verify that the refactoring preserves SQL-visible
behavior across both ordinary and extreme numeric workloads.
---
 src/test/regress/expected/numeric_op_big.out  | 497 ++++++++++++++
 src/test/regress/expected/numeric_op_ddl.out  | 167 +++++
 .../regress/expected/numeric_op_medium.out    | 497 ++++++++++++++
 .../regress/expected/numeric_op_small.out     | 634 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   4 +
 src/test/regress/sql/numeric_op_big.sql       | 267 ++++++++
 src/test/regress/sql/numeric_op_ddl.sql       | 173 +++++
 src/test/regress/sql/numeric_op_medium.sql    | 267 ++++++++
 src/test/regress/sql/numeric_op_small.sql     | 333 +++++++++
 9 files changed, 2839 insertions(+)
 create mode 100644 src/test/regress/expected/numeric_op_big.out
 create mode 100644 src/test/regress/expected/numeric_op_ddl.out
 create mode 100644 src/test/regress/expected/numeric_op_medium.out
 create mode 100644 src/test/regress/expected/numeric_op_small.out
 create mode 100644 src/test/regress/sql/numeric_op_big.sql
 create mode 100644 src/test/regress/sql/numeric_op_ddl.sql
 create mode 100644 src/test/regress/sql/numeric_op_medium.sql
 create mode 100644 src/test/regress/sql/numeric_op_small.sql

diff --git a/src/test/regress/expected/numeric_op_big.out b/src/test/regress/expected/numeric_op_big.out
new file mode 100644
index 00000000000..12c3cf65a83
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_big.out
@@ -0,0 +1,497 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -93674011458
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 49431437020
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | 81596504540
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum      |     sum     |     sum     |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+-------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 103787738890 | 71241136711 | 91238480326 | -25880446966 | 85861504452 | 138111163668 | -64934216831 | -3883271108300 | -3883271108300 | -7965490469700 | -263811485216 | -196222417055 | -253470347100
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+      sum      |     sum      |     sum     |     sum      |      sum      |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+---------------+--------------+-------------+--------------+---------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ -168835944407 | -37026039983 | -9162875272 | 103006726746 | -108905948664 | -77309522276 | 146591765000 | -3883271108300 | -3883271108300 | -7965490469700 | -111504876460 | -183625584196 | -243750883157
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |     sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+--------------+--------------
+ 81596504540 | 81596504540 | -448199909000 |     |     | -448199909000 | 81596504540 | 153330623000 | -1995205883000 | -1995205883000 | 81596504540 | -70370694025 | -81922915700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -153072055232
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -221944827430
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ -87303812940 | -87303812940 | -36200000898
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_800
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_800
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_995
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_995
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_1000
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_1000
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 187915496731
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |      sum      
+-------+---------------
+  2001 | -120277609243
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | -6791326615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum     |     sum      |     sum     |    sum     |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum      |     sum      |     sum      
+-------------+--------------+-------------+------------+-------------+-------------+-------------+----------------+----------------+----------------+--------------+--------------+--------------
+ 69378299805 | -28004625396 | 69165702075 | 6495822165 | 42794676167 | 28778131261 | 23748489343 | -3668417457700 | -3883271108300 | -8180344120300 | 162788189344 | -88035415965 | -14300392972
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+     sum      |    sum    |     sum      |     sum      |     sum      |    sum     |     sum      |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+-----------+--------------+--------------+--------------+------------+--------------+----------------+----------------+----------------+---------------+--------------+---------------
+ 106142378501 | 456865251 | -40344563826 | -82057607022 | -74296237953 | 4474319379 | 111919427572 | -3668417457700 | -3883271108300 | -8180344120300 | -223137274026 | -60159164731 | -114299102966
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |      sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+---------------+--------------
+ -6791326615 | -6791326615 | -448199909000 |     |     | -448199909000 | -6791326615 | 153330623000 | -1995205883000 | -1995205883000 | -6791326615 | -116888674645 | -70370694025
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -23880525307
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 31543863976
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ -87303812940 | -87303812940 | 18056325685
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+     sum      
+--------------
+ -27571036926
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+     sum      
+--------------
+ 142042826843
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
+     sum      
+--------------
+ -37293091674
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_ddl.out b/src/test/regress/expected/numeric_op_ddl.out
new file mode 100644
index 00000000000..d8a1523a5fc
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_ddl.out
@@ -0,0 +1,167 @@
+DROP TABLE IF EXISTS t_9_2;
+NOTICE:  table "t_9_2" does not exist, skipping
+DROP TABLE IF EXISTS t_18_2;
+NOTICE:  table "t_18_2" does not exist, skipping
+DROP TABLE IF EXISTS t_38_2;
+NOTICE:  table "t_38_2" does not exist, skipping
+DROP TABLE IF EXISTS t_80_16;
+NOTICE:  table "t_80_16" does not exist, skipping
+DROP TABLE IF EXISTS t_175_35;
+NOTICE:  table "t_175_35" does not exist, skipping
+DROP TABLE IF EXISTS t_400_80;
+NOTICE:  table "t_400_80" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_200;
+NOTICE:  table "t_1000_200" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_800;
+NOTICE:  table "t_1000_800" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_995;
+NOTICE:  table "t_1000_995" does not exist, skipping
+DROP TABLE IF EXISTS t_base;
+NOTICE:  table "t_base" does not exist, skipping
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+DROP TABLE IF EXISTS t_1000_1000;
+NOTICE:  table "t_1000_1000" does not exist, skipping
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+VACUUM FREEZE ANALYZE t_1000_1000;
diff --git a/src/test/regress/expected/numeric_op_medium.out b/src/test/regress/expected/numeric_op_medium.out
new file mode 100644
index 00000000000..13a0f568726
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_medium.out
@@ -0,0 +1,497 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -41380769803
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -17147548455
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -87880682140
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum      |     sum      |     sum     |     sum     |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+--------------+-------------+-------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 328096480219 | 106894971180 | 29998131312 | 16824177090 | -98601154544 | -84446300842 | -142367027518 | -3883271108300 | -3883271108300 | -7965490469700 | -31882620402 | -214909270261 | -312201369170
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |      sum      |     sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+---------------+--------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+---------------+--------------+---------------
+ -89713139799 | -218213819552 | 100513273638 | -79355358716 | 45044419840 | -3009527768 | 27142855016 | -3883271108300 | -3883271108300 | -7965490469700 | -248734189206 | -85072914373 | -210488126609
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+    sum    |     sum      |     sum      |     sum      |     sum      |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+-----------+--------------+--------------+--------------+--------------+-------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 534102535 | 222800148124 | 252518296200 | -40723661604 | -53446320231 | 49858073732 | 23224917403 | -3883271108300 | -3883271108300 | -7965490469700 | -212735400480 | -257086925922 | -206858526458
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 73062391064
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | 5986675256
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -10341341254
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_175_35
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_175_35
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_400_80
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_400_80
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_200
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_200
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -89558437612
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -19722210906
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 277783880994
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum     |     sum      |     sum      |     sum     |      sum      |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |     sum      
+-------------+--------------+--------------+-------------+---------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+--------------
+ 48377864776 | -49822309848 | 178050220784 | 16824177090 | -138513601794 | 194342415281 | 28892192658 | -3668417457700 | -3883271108300 | -8180344120300 | -151559852522 | -152028559294 | -63268316355
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |     sum      |     sum      |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |     sum      |     sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+--------------+--------------+---------------
+ -67452832312 | -89225471931 | 146580491810 | -79355358716 | -9287051683 | 147260222842 | -70335569319 | -3668417457700 | -3883271108300 | -8180344120300 | -32938146279 | -59204444070 | -108234520264
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+      sum      |      sum      |    sum    |     sum      |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+-----------+--------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -163275209177 | -193968650050 | 679620265 | -40723661604 | -40601882101 | 147411738034 | -193122653902 | -3668417457700 | -3883271108300 | -8180344120300 | 270045436866 | -108867098352 | -172351504959
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -35696475290
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -203092149118
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -75070192124
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+     sum      
+--------------
+ -27666707523
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+     sum     
+-------------
+ 68424688419
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
+     sum      
+--------------
+ -58373747124
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_small.out b/src/test/regress/expected/numeric_op_small.out
new file mode 100644
index 00000000000..1b124ab3dbb
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_small.out
@@ -0,0 +1,634 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 25264177945
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -18318735365
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 77086657878
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 51102838669
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+     sum      |     sum     |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 185332843643 | 74295396054 | -265653763404 | -130951372896 | 33926686039 | -93220527164 | 53982021999 | -3883271108300 | -3883271108300 | -7965490469700 | -136748774422 | -156349489824 | -229516261618
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+     sum      |    sum     |     sum      |      sum      |      sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+------------+--------------+---------------+---------------+---------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 205522402955 | 1737491244 | -98734378124 | -156012333362 | -122318628351 | -265348319222 | 53131151046 | -3883271108300 | -3883271108300 | -7965490469700 | -74716919418 | -132260756970 | -267366558171
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+     sum      |     sum      |      sum      |      sum      |     sum      |     sum      |     sum      |      sum       |      sum       |      sum       |    sum     |      sum      |      sum      
+--------------+--------------+---------------+---------------+--------------+--------------+--------------+----------------+----------------+----------------+------------+---------------+---------------
+ 188179625901 | 112456755410 | -343776078076 | -174825681708 | -71003178193 | 142439459768 | 114154703061 | -3883271108300 | -3883271108300 | -7965490469700 | 8740136096 | -345863766775 | -368207427216
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum     |      sum      |     sum      |      sum      |      sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+-------------+---------------+--------------+---------------+---------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 159659505470 | -1983944819 | -319417093044 | -11104453418 | -158100462876 | -145951162894 | -124997818319 | -3883271108300 | -3883271108300 | -7965490469700 | 146077391644 | -208253897723 | -211323905998
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 47811429266
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -17989105700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -79591213532
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -190878347020
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+             QUERY PLAN              
+-------------------------------------
+ Index Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+               QUERY PLAN                
+-----------------------------------------
+ Index Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 55192112654
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 79276791029
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 119235302735
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 109095039015
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+      sum      |      sum      |      sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum     |     sum      |     sum      
+---------------+---------------+---------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+-------------+--------------+--------------
+ -198398285973 | -268636739265 | -185796812937 | -80212568293 | 40366812062 | 50022231011 | 86359907719 | -3668417457700 | -3883271108300 | -8180344120300 | 88627277998 | -83578376254 | -56047555653
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+      sum      |      sum      |      sum      |     sum      |     sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+---------------+---------------+---------------+--------------+--------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ -198076509004 | -185931021498 | -174261325669 | -62721548562 | -87570938488 | -151149981174 | 46905358609 | -3668417457700 | -3883271108300 | -8180344120300 | 66791978874 | -162795673535 | -140859793615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+      sum      |      sum      |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -173598401621 | -191141226891 | -153991008984 | -174825681708 | -9994590731 | 103039770436 | 90917337737 | -3668417457700 | -3883271108300 | -8180344120300 | 212488760840 | -157402192456 | -222636044392
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum      |     sum      |     sum      |     sum     |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ 179235436643 | 248686171421 | -90086495035 | -11104453418 | 11551494962 | -229139601272 | 32577271698 | -3668417457700 | -3883271108300 | -8180344120300 | 81696122255 | -125106925825 | -150252904102
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -13202507172
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | -696258940
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 19679287493
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | -6889081579
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+     sum      
+--------------
+ -16440552090
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+     sum      
+--------------
+ 104998257177
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+    sum     
+------------
+ 9189839531
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
+      sum      
+---------------
+ -126456520242
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..926010b47cb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -146,3 +146,7 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# this test is to check if numeric operations are correct
+test: numeric_op_ddl
+test: numeric_op_small numeric_op_medium numeric_op_big
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_big.sql b/src/test/regress/sql/numeric_op_big.sql
new file mode 100644
index 00000000000..eb01dfd2091
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_big.sql
@@ -0,0 +1,267 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+reset enable_seqscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_ddl.sql b/src/test/regress/sql/numeric_op_ddl.sql
new file mode 100644
index 00000000000..5d213c75acd
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_ddl.sql
@@ -0,0 +1,173 @@
+DROP TABLE IF EXISTS t_9_2;
+DROP TABLE IF EXISTS t_18_2;
+DROP TABLE IF EXISTS t_38_2;
+DROP TABLE IF EXISTS t_80_16;
+DROP TABLE IF EXISTS t_175_35;
+DROP TABLE IF EXISTS t_400_80;
+DROP TABLE IF EXISTS t_1000_200;
+DROP TABLE IF EXISTS t_1000_800;
+DROP TABLE IF EXISTS t_1000_995;
+
+DROP TABLE IF EXISTS t_base;
+
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+
+DROP TABLE IF EXISTS t_1000_1000;
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_1000_1000;
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_medium.sql b/src/test/regress/sql/numeric_op_medium.sql
new file mode 100644
index 00000000000..dd3802e79c1
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_medium.sql
@@ -0,0 +1,267 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+reset enable_seqscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_small.sql b/src/test/regress/sql/numeric_op_small.sql
new file mode 100644
index 00000000000..c90537d6151
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_small.sql
@@ -0,0 +1,333 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+reset enable_seqscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: [PATCH v2] Make NumericVar storage semantics explicit
@ 2026-05-11 10:55  =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  parent: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: =?utf-8?B?Q2hlbmh1aU1v?= @ 2026-05-11 10:55 UTC (permalink / raw)
  To: pgsql-hackers; =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>

Hi all,

Attached is v2 of this patch.

CI/UBSan exposed a bug in the previous version&nbsp;of&nbsp;set_var_from_var():
a finite zero NumericVar may have ndigits == 0 and digits == NULL, so the
old code could still pass a NULL pointer to memcpy() with a zero copy
length. This has been fixed in v2.

No other design changes are intended in this update; the attached patches
are refreshed complete versions of the series.

Thanks.
Regards,
Chenhui Mo
         Original
         
       
From: ChenhuiMo <[email protected]&gt;
Date: May 10, 2026 22:12
To: pgsql-hackers <[email protected]&gt;
Subject: [PATCH] Make NumericVar storage semantics explicit



Hi all,


This patch aims to make the memory and ownership semantics of NumericVar
explicit in PostgreSQL, and to reorganize the related helper functions
accordingly, so that the rules for digit-buffer acquisition, release,
borrowing, and writing become clearer, more consistent, and easier
to maintain.


This patch does not change the mathematical algorithms used by numeric
itself. It focuses on the memory model, state transitions, and ownership
semantics of NumericVar and the related helper paths, and adjusts those
paths as needed so that they match the new model.


This patch is also not a performance patch. It does not introduce
aggressive performance optimizations, nor does it attempt to rewrite
all higher-level numeric routines in this round. Instead, its focus is to:


- make previously scattered implicit contracts explicit;
- make the storage / borrow / own semantics of NumericVar easier to
&nbsp; &nbsp; understand and maintain;
- actually connect this new model to the core comparison, hashing,
&nbsp; &nbsp; and basic arithmetic paths;
- verify through regression tests that SQL-visible behavior remains unchanged.


Although this patch is not aimed at improving performance, a clearer
and more explicit memory model can provide a more solid foundation for
future internal numeric optimizations. The related tests also indicate
that this refactoring does not introduce any visible performance regression.


Background and motivation
-------------------------


NumericVar is the in-memory working representation of PostgreSQL's numeric
type. It is not the packed Numeric datum stored in the database itself, but
the working representation used by internal paths such as comparison,
arithmetic, and aggregation.


In the current implementation, there are already several different memory
semantics around NumericVar, for example:


- a variable may be in an "empty" state, with no active digit storage;
- a variable may borrow digits from an external Numeric datum as a read-only view;
- a variable may own a writable digit buffer for internal arithmetic operations;
- in some paths, digit storage comes from a fixed-size inline buffer;
- in some paths, digit storage comes from heap allocation.


These conventions already exist in the code, but they remain largely
implicit rather than being expressed as a single, explicit internal
contract. This makes ownership, mutability, and buffer-transition rules
harder to reason about, especially when switching between packed Numeric
datums and the working representation.


Accordingly, the goal of this patch is not to introduce new numeric
algorithms, but to first make these already-existing but insufficiently
documented memory semantics explicit and consistent.


The new memory model
--------------------


This patch introduces a more explicit NumericVar storage-state model to
describe the meaning of the current digit storage of a NumericVar.


In addition to the existing numeric metadata, NumericVar now has a few
state-related fields, including:


capacity: the physical capacity of the current owned digit buffer;
state: the current storage state of the NumericVar;
borrow_kind: in borrowed state, which kind of storage the current digits
are borrowed from;
inline_buf: whether the variable is associated with a fixed-size inline home.


This patch also introduces BufferedNumericVar, which wraps a NumericVar
together with a fixed-size inline buffer, used for:


inline writable digit storage;
or, when needed, as workspace to hold a detoasted numeric datum.


It is important to emphasize that BufferedNumericVar is not a new SQL-visible
representation. It is only an internal helper structure used by numeric to
support the new memory model.


The new storage states are:


NUMVAR_EMPTY
NUMVAR_BORROWED
NUMVAR_OWNED_INLINE
NUMVAR_OWNED_HEAP


For borrowed state, this patch further distinguishes:


NUMVAR_BORROW_EXTERNAL
NUMVAR_BORROW_INLINE


This makes it possible to distinguish more clearly between:


borrowing an external Numeric datum or static constant representation;
borrowing a detoasted numeric representation currently residing in a
BufferedNumericVar inline buffer.


Core helpers and interface semantics


Under the new model, the related helpers are reorganized into clearer layers:


- raw storage helpers:
&nbsp; &nbsp; only responsible for low-level raw digit-storage allocation;
- storage-state helpers:
&nbsp; &nbsp; responsible for transitions among borrowed / owned / inline / heap states;
- variable-level helpers:
&nbsp; &nbsp; responsible for NumericVar initialization, copying,
&nbsp; &nbsp; buffer acquisition, and release;
- result preparation / finalization helpers:
&nbsp; &nbsp; responsible for alias-safe result preparation and commit
&nbsp; &nbsp; during arithmetic operations.


The purpose of this split is to make the already-existing but previously
scattered rules clearer, not to introduce new SQL-visible behavior.


Core paths already using the new model
--------------------------------------


This patch does not merely introduce unused infrastructure.
The new model is already used in core numeric execution paths.


The SQL-visible core entry points already using the new model include:


cmp_numerics
numeric_hash
numeric_add
numeric_sub
numeric_mul
numeric_div


This means that:


comparison paths already use the explicit state / borrowed / owned semantics;
hashing paths already interact with the new initialization / borrow semantics;
the basic arithmetic paths already interact with the new state model
through the new result-preparation / result-finalization helpers.


The lower-level variable arithmetic bodies such as
add_var/sub_var/mul_var/div_var are not completely
rewritten into a fully new style in this patch, but their
result preparation, alias-safe behavior, and result finalization
are already integrated with the explicit state model through the
new helper framework.


More complex higher-level numeric routines such as sqrt, ln, log,
and power still largely keep their original algorithmic organization
in this patch, and are only adjusted as needed to remain compatible
with the new helper semantics. This is an intentional choice: this
patch focuses first on making the underlying NumericVar contract
explicit and on integrating the new model into core paths, rather
than trying to rewrite all higher-level numeric routines at once.


Testing
-------


To verify that this refactoring does not change SQL-visible behavior,
this patch adds and reorganizes a fairly broad set of regression tests.


The tests cover multiple precision / scale combinations, including:


NUMERIC(9, 2)
NUMERIC(18, 2)
NUMERIC(38, 2)
NUMERIC(80, 16)
NUMERIC(175, 35)
NUMERIC(400, 80)
NUMERIC(1000, 200)
NUMERIC(1000, 800)
NUMERIC(1000, 995)
NUMERIC(1000, 1000)


The test coverage includes:


basic arithmetic operations;
composite expressions;
comparison-sensitive paths;
window functions;
join-driven expressions;
aggregate expressions;
post-update verification;
error cases;
index / index-only related paths.


For excessively long outputs, digest-based verification is used so
that the expected files are not dominated by very large numeric literals.


In addition to the new and adjusted numeric-related tests, the
patch also passes the full regression test suite, confirming that:


the new state model and helper refactoring do not break existing behavior;
higher-level numeric paths still work correctly after being adapted
to the new helper semantics.


I would especially appreciate feedback on:


- whether this storage-state model makes the existing NumericVar
&nbsp; &nbsp; contract clearer;
- whether this helper split is sensible;
- whether the borrowed versus owned terminology is clear enough;
- whether the amount of detail in the updated comments and invariants
&nbsp; &nbsp; is appropriate.


Thanks for reading.


This series is split into two patches:


make NumericVar storage states explicit and connect
the new model to core numeric paths add regression coverage


I would especially appreciate feedback on the state model,
helper split, and terminology.


Regards,
Chenhui Mo

Attachments:

  [application/octet-stream] 0001-numeric-make-NumericVar-storage-states-explicit-and-.patch (56.2K, ../../[email protected]/3-0001-numeric-make-NumericVar-storage-states-explicit-and-.patch)
  download | inline diff:
From 2e5409dd3eee75c48442b8926a3f7690c1f91c9e Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:42:17 +0800
Subject: [PATCH 1/2] numeric: make NumericVar storage states explicit and use
 them in core paths

Clarify the in-memory storage model of NumericVar by making buffer
ownership and mutability states explicit, and reorganize the related
helper layer around that model.

This introduces explicit storage states for empty, borrowed,
owned-inline, and owned-heap representations, together with
borrow-kind tracking for borrowed digit views. It also adds
BufferedNumericVar as the inline-home wrapper used by the new model.

The patch then connects the new helper semantics to core comparison,
hashing, and basic arithmetic paths, while keeping higher-level numeric
algorithms structurally close to the existing implementation.

The goal is not to alter SQL-visible numeric behavior, but to make the
existing internal contract around NumericVar storage easier to
understand, document, and maintain.
---
 src/backend/utils/adt/numeric.c | 1198 ++++++++++++++++++++++++-------
 1 file changed, 950 insertions(+), 248 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..b92935c624f 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -104,6 +104,7 @@ typedef int16 NumericDigit;
 #endif
 
 #define NBASE_SQR	(NBASE * NBASE)
+#define NUMERIC_INLINE_BUFFER_SIZE 128
 
 /*
  * The Numeric type as stored on disk.
@@ -261,66 +262,165 @@ struct NumericData
 #define NUMERIC_WEIGHT_MAX			PG_INT16_MAX
 
 /* ----------
- * NumericVar is the format we use for arithmetic.  The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array.  If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight.  Another way to say it is that there are weight+1
- * digits before the decimal point.  It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar.  digits points at the first digit in actual use (the one
- * with the specified weight).  We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space.  We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present.  NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
+ * NumericVar is the in-memory working representation of a numeric value.
+ *
+ * A NumericVar always stores the logical numeric metadata (ndigits, weight,
+ * sign, dscale), but the physical storage backing digits[] is stateful.
+ *
+ * There are four storage states:
+ *
+ * 1. NUMVAR_EMPTY
+ *    The variable carries no digits storage and no digit payload.
+ *
+ * 2. NUMVAR_BORROWED
+ *    digits points to storage owned by some other representation.
+ *    In this state the current NumericVar does not own writable digit
+ *    storage, and buf is NULL.
+ *
+ *    Borrowed storage is further classified by borrow_kind:
+ *
+ *    - NUMVAR_BORROW_EXTERNAL
+ *      digits points into external numeric storage, such as a detoasted
+ *      Numeric datum.  This storage is read-only from the NumericVar's
+ *      perspective and cannot be upgraded in-place to writable inline
+ *      storage.
+ *
+ *    - NUMVAR_BORROW_INLINE
+ *      digits points into numeric storage currently materialized inside the
+ *      inline buffer of an associated BufferedNumericVar.  This is still a
+ *      read-only borrowed view, but because the underlying memory belongs to
+ *      the same buffered home, it may later be converted into writable inline
+ *      working storage.
+ *
+ * 3. NUMVAR_OWNED_INLINE
+ *    buf points to the inline buffer associated with this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * 4. NUMVAR_OWNED_HEAP
+ *    buf points to palloc'd writable storage owned by this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * In owned states, buf is the base of the current writable digit storage and
+ * digits points to the logical first digit.  As in the historical NumericVar
+ * representation, owned storage is initially allocated with a spare leading
+ * digit so that carry propagation can enlarge the value without immediate
+ * reallocation.  Stored numeric datums do not have this extra working space;
+ * it exists only in the in-memory working representation.  Therefore, a
+ * borrowed view of a stored/detoasted Numeric datum must not be treated as a
+ * writable work buffer without an explicit state transition.
+ *
+ * Note, however, that digits is not required to remain equal to buf + 1 for
+ * the lifetime of an owned NumericVar.  Subsequent normalization steps such as
+ * stripping leading zeroes may advance digits within the owned buffer, as long
+ * as the active digit range remains within the storage owned by buf.
+ *
+ * inline_buf, when non-NULL, identifies the fixed-size inline home provided by
+ * BufferedNumericVar.  It does not by itself imply that the variable is
+ * currently in NUMVAR_OWNED_INLINE state.  For example, a NumericVar may be in
+ * NUMVAR_BORROWED or NUMVAR_OWNED_HEAP state while still retaining an inline
+ * home for possible future reuse.
+ *
+ * capacity is the physical capacity of the current owned buffer, including the
+ * spare leading digit.  It is meaningful only in owned states, and is zero in
+ * empty or borrowed states.
+ *
+ * As with the traditional implementation, variable-level functions should be
+ * written so that a NumericVar may safely be used as both input and
+ * destination.  State transitions must preserve that property.
+ *
+ * NumericVar state invariants:
+ *
+ * NUMVAR_EMPTY:
+ *     buf == NULL
+ *     digits == NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *
+ * NUMVAR_BORROWED:
+ *     buf == NULL
+ *     borrow_kind != NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *     digits points to borrowed storage, unless sign is special
+ *
+ * NUMVAR_OWNED_INLINE:
+ *     buf == inline_buf
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == NUMERIC_INLINE_BUFFER_SIZE
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * NUMVAR_OWNED_HEAP:
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity > 0
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * Special values are represented through sign, not through a separate storage
+ * state.  For NaN and infinities, only sign is semantically meaningful.
  * ----------
  */
+
+typedef enum NUMERIC_VAR_STATUS
+{
+	NUMVAR_EMPTY,
+	NUMVAR_BORROWED,
+	NUMVAR_OWNED_INLINE,
+	NUMVAR_OWNED_HEAP
+}			NUMERIC_VAR_STATUS;
+
+typedef enum NUMERIC_BORROW_KIND
+{
+	NUMVAR_BORROW_NONE,
+	NUMVAR_BORROW_EXTERNAL,
+	NUMVAR_BORROW_INLINE
+}			NUMERIC_BORROW_KIND;
+
 typedef struct NumericVar
 {
-	int			ndigits;		/* # of digits in digits[] - can be 0! */
+	/* logical numeric metadata */
+	int			ndigits;		/* # of digits in digits[]; may be 0 */
 	int			weight;			/* weight of first digit */
 	int			sign;			/* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
 	int			dscale;			/* display scale */
-	NumericDigit *buf;			/* start of palloc'd space for digits[] */
-	NumericDigit *digits;		/* base-NBASE digits */
+
+	/* storage state metadata */
+	NUMERIC_VAR_STATUS state;	/* current storage state */
+	NUMERIC_BORROW_KIND borrow_kind;	/* source of borrowed digits, if
+										 * borrowed */
+	int			capacity;		/* current owned buffer capacity, spare digit
+								 * included */
+
+	/* current digit storage */
+	NumericDigit *buf;			/* base of current owned writable storage, or
+								 * NULL */
+	NumericDigit *digits;		/* logical first base-NBASE digit, or NULL */
+
+	/* optional fixed-size inline home */
+	NumericDigit *inline_buf;	/* inline buffer of associated
+								 * BufferedNumericVar, or NULL */
 } NumericVar;
 
+#define NUMERIC_VARLENA_SIZE (VARHDRSZ / sizeof(NumericDigit) + \
+	(sizeof(uint16) + sizeof(int16)) / sizeof(NumericDigit) + \
+	NUMERIC_INLINE_BUFFER_SIZE + DIV_GUARD_DIGITS + 4)
+
+typedef struct BufferedNumericVar
+{
+	NumericVar	var;
+	int64		magic;			/* used for align with 8-byte */
+	NumericDigit buffer[NUMERIC_VARLENA_SIZE];
+}			BufferedNumericVar;
+
+
+#define BUFFERED_NUMERIC_VAR_MAGIC 0x4E554D455249435F	/* 'NUMERIC_MAGIC' */
+
+#define NUMERICVAR_IS_SPECIAL(v) \
+    ((v)->sign == NUMERIC_NAN || (v)->sign == NUMERIC_PINF || (v)->sign == NUMERIC_NINF)
+
 
 /* ----------
  * Data for generate_series
@@ -415,18 +515,18 @@ typedef struct NumericSumAccum
  */
 static const NumericDigit const_zero_data[1] = {0};
 static const NumericVar const_zero =
-{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data};
+{0, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_data, NULL};
 
 static const NumericDigit const_one_data[1] = {1};
 static const NumericVar const_one =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericVar const_minus_one =
-{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_NEG, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericDigit const_two_data[1] = {2};
 static const NumericVar const_two =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_two_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_zero_point_nine_data[1] = {9000};
@@ -436,7 +536,7 @@ static const NumericDigit const_zero_point_nine_data[1] = {90};
 static const NumericDigit const_zero_point_nine_data[1] = {9};
 #endif
 static const NumericVar const_zero_point_nine =
-{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data};
+{1, -1, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_point_nine_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_one_point_one_data[2] = {1, 1000};
@@ -446,28 +546,31 @@ static const NumericDigit const_one_point_one_data[2] = {1, 10};
 static const NumericDigit const_one_point_one_data[2] = {1, 1};
 #endif
 static const NumericVar const_one_point_one =
-{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data};
+{2, 0, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_point_one_data, NULL};
 
 static const NumericVar const_nan =
-{0, 0, NUMERIC_NAN, 0, NULL, NULL};
+{0, 0, NUMERIC_NAN, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_pinf =
-{0, 0, NUMERIC_PINF, 0, NULL, NULL};
+{0, 0, NUMERIC_PINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_ninf =
-{0, 0, NUMERIC_NINF, 0, NULL, NULL};
+{0, 0, NUMERIC_NINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 #if DEC_DIGITS == 4
 static const int round_powers[4] = {0, 1000, 100, 10};
 #endif
 
-
 /* ----------
  * Local functions
  * ----------
  */
 
 #ifdef NUMERIC_DEBUG
+static inline int check_digit_overflow(const NumericVar *var);
+static inline void check_numericvar_state(const NumericVar *var, const char *where);
+#define CHECK_NUMERICVAR(v) check_numericvar_state((v), __func__)
+#define CHECK_NUMERICVAR_AT(v, where) check_numericvar_state((v), (where))
 static void dump_numeric(const char *str, Numeric num);
 static void dump_var(const char *str, NumericVar *var);
 #else
@@ -475,15 +578,65 @@ static void dump_var(const char *str, NumericVar *var);
 #define dump_var(s,v)
 #endif
 
-#define digitbuf_alloc(ndigits)  \
-	((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit)))
-#define digitbuf_free(buf)	\
-	do { \
-		 if ((buf) != NULL) \
-			 pfree(buf); \
-	} while (0)
+/*
+ * digitbuf_alloc_raw() -
+ *
+ * Allocate raw heap-backed digit storage with the specified physical
+ * capacity.
+ *
+ * The capacity is measured in NumericDigit units and includes any spare
+ * leading digit space required by the caller.
+ *
+ * This is a low-level storage primitive.  It does not inspect or update any
+ * NumericVar state, and it does not by itself establish a complete working
+ * NumericVar representation.
+ */
+static inline NumericDigit *
+digitbuf_alloc_raw(int sz)
+{
+	return (NumericDigit *) palloc(sizeof(NumericDigit) * sz);
+}
 
-#define init_var(v)		memset(v, 0, sizeof(NumericVar))
+/*
+ * init_var() -
+ *
+ * Initialize a NumericVar in NUMVAR_EMPTY storage state, with no active digit
+ * storage, no borrowed digit view, and no current digit payload.
+ *
+ * This establishes a clean starting point for later transitions into
+ * borrowed or owned states.
+ */
+static inline void
+init_var(NumericVar *v)
+{
+	memset(v, 0, sizeof(NumericVar));
+}
+
+/*
+ * init_buffered_var() -
+ *
+ * Initialize a BufferedNumericVar so that its embedded NumericVar starts with
+ * writable inline working storage backed by the object's fixed-size inline
+ * buffer.
+ *
+ * The inline buffer is installed as the current owned storage, and the digit
+ * pointer is initialized to the normal working position after the spare
+ * leading digit.
+ */
+static void
+init_buffered_var(BufferedNumericVar * bvar)
+{
+	bvar->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&bvar->var);
+
+	bvar->var.inline_buf = bvar->buffer;
+	bvar->var.capacity = NUMERIC_INLINE_BUFFER_SIZE;
+	bvar->var.state = NUMVAR_OWNED_INLINE;
+	bvar->var.borrow_kind = NUMVAR_BORROW_NONE;
+	bvar->var.buf = bvar->buffer;
+	bvar->var.buf[0] = 0;
+	bvar->var.digits = bvar->buffer + 1;
+}
 
 #define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
 	(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
@@ -494,8 +647,27 @@ static void dump_var(const char *str, NumericVar *var);
 	(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
 	(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
 
+static void digitbuf_free(NumericVar *var);
+
+static NumericDigit *digitbuf_get(NumericVar *var,
+								  int require_ndigits);
+
 static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
+
+static bool result_aliases_input(const NumericVar *result,
+								 const NumericVar *a,
+								 const NumericVar *b);
+
+static NumericVar *prepare_result_var(NumericVar *result,
+									  const NumericVar *a,
+									  const NumericVar *b,
+									  int res_ndigits,
+									  BufferedNumericVar * tmp);
+
+static void finish_result_var(NumericVar *work_result,
+							  NumericVar *result);
+
 static void zero_var(NumericVar *var);
 
 static bool set_var_from_str(const char *str, const char *cp,
@@ -507,7 +679,10 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
 												 const char **endptr,
 												 Node *escontext);
 static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_buffered_var_from_num(Numeric src, BufferedNumericVar * bvar);
+static varlena *numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+											   size_t buffer_size);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
 static char *get_str_from_var(const NumericVar *var);
 static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -543,6 +718,7 @@ static int	cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
 						   int var1weight, int var1sign,
 						   const NumericDigit *var2digits, int var2ndigits,
 						   int var2weight, int var2sign);
+
 static void add_var(const NumericVar *var1, const NumericVar *var2,
 					NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -2416,8 +2592,8 @@ numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
 Datum
 numeric_cmp(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	int			result;
 
 	result = cmp_numerics(num1, num2);
@@ -2428,12 +2604,11 @@ numeric_cmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
-
 Datum
 numeric_eq(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) == 0;
@@ -2447,8 +2622,8 @@ numeric_eq(PG_FUNCTION_ARGS)
 Datum
 numeric_ne(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) != 0;
@@ -2462,8 +2637,8 @@ numeric_ne(PG_FUNCTION_ARGS)
 Datum
 numeric_gt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) > 0;
@@ -2477,8 +2652,8 @@ numeric_gt(PG_FUNCTION_ARGS)
 Datum
 numeric_ge(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) >= 0;
@@ -2492,8 +2667,8 @@ numeric_ge(PG_FUNCTION_ARGS)
 Datum
 numeric_lt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) < 0;
@@ -2507,8 +2682,8 @@ numeric_lt(PG_FUNCTION_ARGS)
 Datum
 numeric_le(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) <= 0;
@@ -2523,6 +2698,11 @@ static int
 cmp_numerics(Numeric num1, Numeric num2)
 {
 	int			result;
+	BufferedNumericVar var1;
+	BufferedNumericVar var2;
+
+	num1 = init_buffered_var_from_num(num1, &var1);
+	num2 = init_buffered_var_from_num(num2, &var2);
 
 	/*
 	 * We consider all NANs to be equal and larger than any non-NAN (including
@@ -2714,7 +2894,7 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
 Datum
 hash_numeric(PG_FUNCTION_ARGS)
 {
-	Numeric		key = PG_GETARG_NUMERIC(0);
+	Numeric		key = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
 	Datum		digit_hash;
 	Datum		result;
 	int			weight;
@@ -2723,6 +2903,9 @@ hash_numeric(PG_FUNCTION_ARGS)
 	int			i;
 	int			hash_len;
 	NumericDigit *digits;
+	BufferedNumericVar var;
+
+	key = init_buffered_var_from_num(key, &var);
 
 	/* If it's NaN or infinity, don't try to hash the rest of the fields */
 	if (NUMERIC_IS_SPECIAL(key))
@@ -2865,12 +3048,16 @@ hash_numeric_extended(PG_FUNCTION_ARGS)
 Datum
 numeric_add(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_add_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2882,11 +3069,15 @@ numeric_add(PG_FUNCTION_ARGS)
 Numeric
 numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+
+	BufferedNumericVar result;
 	Numeric		res;
 
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2915,18 +3106,15 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_ninf);
 	}
 
-	/*
-	 * Unpack the values, let add_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
 
-	init_var(&result);
-	add_var(&arg1, &arg2, &result);
+	add_var(&arg1.var, &arg2.var, &result.var);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -2940,12 +3128,16 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_sub(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_sub_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2958,11 +3150,17 @@ numeric_sub(PG_FUNCTION_ARGS)
 Numeric
 numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let sub_var() compute the result and return it.
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2991,18 +3189,14 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_pinf);
 	}
 
-	/*
-	 * Unpack the values, let sub_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	sub_var(&arg1.var, &arg2.var, &result.var);
 
-	init_var(&result);
-	sub_var(&arg1, &arg2, &result);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3016,12 +3210,16 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_mul(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_mul_safe(num1, num2, fcinfo->context);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
 		PG_RETURN_NULL();
 
@@ -3037,11 +3235,25 @@ numeric_mul(PG_FUNCTION_ARGS)
 Numeric
 numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let mul_var() compute the result and return it.
+	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
+	 * case of numeric_mul(), which is invoked for the * operator on numerics,
+	 * we request exact representation for the product (rscale = sum(dscale of
+	 * arg1, dscale of arg2)).  If the exact result has more digits after the
+	 * decimal point than can be stored in a numeric, we round it.  Rounding
+	 * after computing the exact result ensures that the final result is
+	 * correctly rounded (rounding in mul_var() using a truncated product
+	 * would not guarantee this).
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3102,29 +3314,17 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 		Assert(false);
 	}
 
-	/*
-	 * Unpack the values, let mul_var() compute the result and return it.
-	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
-	 * case of numeric_mul(), which is invoked for the * operator on numerics,
-	 * we request exact representation for the product (rscale = sum(dscale of
-	 * arg1, dscale of arg2)).  If the exact result has more digits after the
-	 * decimal point than can be stored in a numeric, we round it.  Rounding
-	 * after computing the exact result ensures that the final result is
-	 * correctly rounded (rounding in mul_var() using a truncated product
-	 * would not guarantee this).
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	mul_var(&arg1.var, &arg2.var, &result.var, arg1.var.dscale + arg2.var.dscale);
 
-	init_var(&result);
-	mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
+	if (result.var.dscale > NUMERIC_DSCALE_MAX)
+		round_var(&result.var, NUMERIC_DSCALE_MAX);
 
-	if (result.dscale > NUMERIC_DSCALE_MAX)
-		round_var(&result, NUMERIC_DSCALE_MAX);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3138,12 +3338,16 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_div(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_div_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -3156,12 +3360,18 @@ numeric_div(PG_FUNCTION_ARGS)
 Numeric
 numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 	int			rscale;
 
+	/*
+	 * Unpack the arguments
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3209,31 +3419,27 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_zero);
 	}
 
-	/*
-	 * Unpack the arguments
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
-
-	init_var(&result);
+	init_buffered_var(&result);
 
 	/*
 	 * Select scale for division result
 	 */
-	rscale = select_div_scale(&arg1, &arg2);
+	rscale = select_div_scale(&arg1.var, &arg2.var);
 
 	/* Check for division by zero */
-	if (arg2.ndigits == 0 || arg2.digits[0] == 0)
+	if (arg2.var.ndigits == 0 || arg2.var.digits[0] == 0)
 		goto division_by_zero;
 
 	/*
 	 * Do the divide and return the result
 	 */
-	div_var(&arg1, &arg2, &result, rscale, true, true);
+	div_var(&arg1.var, &arg2.var, &result.var, rscale, true, true);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 
@@ -3450,17 +3656,17 @@ numeric_inc(PG_FUNCTION_ARGS)
 Datum
 numeric_smaller(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) < 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -3472,17 +3678,17 @@ numeric_smaller(PG_FUNCTION_ARGS)
 Datum
 numeric_larger(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) > 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -4854,7 +5060,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	state->N++;
 
 	/* Accumulate sums */
@@ -4862,7 +5067,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	if (state->calcSumX2)
 		accum_sum_add(&(state->sumX2), &X2);
-
 	MemoryContextSwitchTo(old_context);
 }
 
@@ -4932,7 +5136,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 			return false;
 		}
 	}
-
 	/* if we need X^2, calculate that in short-lived context */
 	if (state->calcSumX2)
 	{
@@ -4942,7 +5145,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	if (state->N-- > 1)
 	{
 		/* Negate X, to subtract it from the sum */
@@ -4960,14 +5162,11 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 	{
 		/* Zero the sums */
 		Assert(state->N == 0);
-
 		accum_sum_reset(&state->sumX);
 		if (state->calcSumX2)
 			accum_sum_reset(&state->sumX2);
 	}
-
 	MemoryContextSwitchTo(old_context);
-
 	return true;
 }
 
@@ -6689,6 +6888,80 @@ dump_var(const char *str, NumericVar *var)
 
 	printf("\n");
 }
+
+
+static inline int
+check_digit_overflow(const NumericVar *var)
+{
+	int			i;
+
+	for (i = 0; i < var->ndigits; i++)
+	{
+		if (var->digits[i] >= NBASE)
+		{
+			return 1;
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * check_numericvar_state() -
+ *
+ * Validate internal NumericVar storage-state invariants.
+ *
+ * This is primarily intended as a debugging aid for future changes to the
+ * NumericVar storage-state machinery.
+ */
+static inline void
+check_numericvar_state(const NumericVar *var, const char *where)
+{
+	Assert(var != NULL);
+	Assert(var->ndigits >= 0);
+
+	if (var->state != NUMVAR_BORROWED)
+		Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->buf == NULL);
+			Assert(var->digits == NULL);
+			Assert(var->capacity == 0);
+			break;
+
+		case NUMVAR_BORROWED:
+			Assert(var->buf == NULL);
+			Assert(var->capacity == 0);
+			Assert(var->borrow_kind != NUMVAR_BORROW_NONE);
+
+			if (!NUMERICVAR_IS_SPECIAL(var))
+				Assert(var->digits != NULL);
+			break;
+
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->inline_buf != NULL);
+			Assert(var->buf == var->inline_buf);
+			Assert(var->digits != NULL);
+			Assert(var->capacity == NUMERIC_INLINE_BUFFER_SIZE);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->buf != NULL);
+			Assert(var->digits != NULL);
+			Assert(var->capacity > 0);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		default:
+			elog(ERROR, "unexpected NumericVar state %d at %s",
+				 (int) var->state, where);
+	}
+}
 #endif							/* NUMERIC_DEBUG */
 
 
@@ -6703,35 +6976,296 @@ dump_var(const char *str, NumericVar *var)
  * ----------------------------------------------------------------------
  */
 
+/*
+ * digitbuf_get() -
+ *
+ * Ensure that a NumericVar has owned writable digit storage with at least the
+ * requested physical capacity, including any spare leading digit required by
+ * the caller.
+ *
+ * Regardless of the variable's current state, this function returns the base
+ * of writable owned storage and leaves the variable in either
+ * NUMVAR_OWNED_INLINE or NUMVAR_OWNED_HEAP state.
+ *
+ * This function is concerned only with storage acquisition and state
+ * transition.  Callers remain responsible for maintaining the logical numeric
+ * fields and for copying digit contents when required.
+ */
+static inline NumericDigit *
+digitbuf_get(NumericVar *var, int required_capacity)
+{
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf)
+			{
+				var->buf = var->inline_buf;
+				var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+				var->state = NUMVAR_OWNED_INLINE;
+			}
+			else
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_BORROWED:
+			if (var->borrow_kind == NUMVAR_BORROW_EXTERNAL)
+			{
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf != NULL)
+				{
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			else
+			{
+				Assert(var->inline_buf != NULL);
+				Assert(var->borrow_kind == NUMVAR_BORROW_INLINE);
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE)
+				{
+					/*
+					 * NUMVAR_BORROW_INLINE is currently used only for the
+					 * special case where the inline buffer of a
+					 * BufferedNumericVar holds a detoasted Numeric datum.
+					 *
+					 * In this case, the caller guarantees that the borrowed
+					 * buffered representation does not need to be preserved
+					 * as an independent read-only view. Therefore, when the
+					 * requested capacity fits in the inline home, we may
+					 * convert that storage to NUMVAR_OWNED_INLINE by
+					 * resetting buf to the beginning of the inline buffer and
+					 * reestablishing the normal NumericVar working-buffer
+					 * layout there.
+					 */
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			break;
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			Assert(var->inline_buf != NULL);
+			if (required_capacity > var->capacity)
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity > var->capacity)
+			{
+				pfree(var->buf);
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+			}
+			break;
+		default:
+			Assert(false);
+			return NULL;		/* silence compiler warning */
+	}
+
+	var->borrow_kind = NUMVAR_BORROW_NONE;
+	var->digits = var->buf + 1;
+
+	return var->buf;
+}
+
+
+/*
+ * digitbuf_free() -
+ *
+ * Release the current active digit storage or borrowed digit view of a
+ * NumericVar and return it to NUMVAR_EMPTY storage state.
+ *
+ * Heap-backed owned storage is pfree'd.  Inline owned storage is simply
+ * detached as the active buffer and remains available as the variable's
+ * inline home.  Borrowed storage is not freed, but any active borrowed view
+ * is dropped.
+ *
+ * This function resets only storage-state fields.  It does not by itself
+ * establish a meaningful numeric value.
+ */
+static inline void
+digitbuf_free(NumericVar *var)
+{
+	switch (var->state)
+	{
+		case NUMVAR_OWNED_HEAP:
+			pfree(var->buf);
+			pg_fallthrough;
+		case NUMVAR_EMPTY:
+		case NUMVAR_BORROWED:
+		case NUMVAR_OWNED_INLINE:
+			var->buf = NULL;
+			var->capacity = 0;
+			var->digits = NULL;
+			var->state = NUMVAR_EMPTY;
+			var->borrow_kind = NUMVAR_BORROW_NONE;
+			break;
+		default:
+			Assert(false);
+	}
+}
+
+
+/*
+ * result_aliases_input() -
+ *
+ * Return true if the destination variable cannot be used directly as the
+ * output of an arithmetic operation because it aliases one of the inputs.
+ *
+ * In addition to direct pointer equality, this treats result as aliasing if
+ * its current digit pointer falls within the active digit range of either
+ * input variable.
+ */
+static bool
+result_aliases_input(const NumericVar *result,
+					 const NumericVar *a,
+					 const NumericVar *b)
+{
+	if (result == a || result == b)
+		return true;
+
+	/*
+	 * Be conservative: if result currently points into either input digit
+	 * range, treat it as aliasing as well.
+	 */
+	if (result->digits != NULL)
+	{
+		if (a->digits != NULL &&
+			result->digits >= a->digits &&
+			result->digits < a->digits + a->ndigits)
+			return true;
+
+		if (b->digits != NULL &&
+			result->digits >= b->digits &&
+			result->digits < b->digits + b->ndigits)
+			return true;
+	}
+
+	return false;
+}
+
+
+/*
+ * prepare_result_var() -
+ *
+ * Prepare a writable result variable for an arithmetic operation.
+ *
+ * If the caller's destination does not alias either input, writable storage
+ * is allocated directly in that destination and the destination is returned.
+ *
+ * If the destination aliases an input, a temporary BufferedNumericVar is
+ * initialized and allocated instead, so that the operation may read its
+ * inputs without destroying their digit storage.
+ */
+static NumericVar *
+prepare_result_var(NumericVar *result,
+				   const NumericVar *a,
+				   const NumericVar *b,
+				   int res_ndigits,
+				   BufferedNumericVar * tmp)
+{
+	if (result_aliases_input(result, a, b))
+	{
+		init_buffered_var(tmp);
+
+		alloc_var(&tmp->var, res_ndigits);
+
+		return &tmp->var;
+	}
+
+	alloc_var(result, res_ndigits);
+
+	return result;
+}
+
+
+/*
+ * finish_result_var() -
+ *
+ * Finalize a result prepared by prepare_result_var().
+ *
+ * If the arithmetic result was computed directly in the caller's destination,
+ * this function does nothing.
+ *
+ * If the result was computed in a temporary variable, copy that value back
+ * into the caller's destination and release the temporary storage.
+ */
+static void
+finish_result_var(NumericVar *work_result, NumericVar *result)
+{
+	if (work_result != result)
+	{
+		set_var_from_var(work_result, result);
+		free_var(work_result);
+	}
+}
+
 
 /*
  * alloc_var() -
  *
- *	Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
+ * Allocate owned writable digit storage for a NumericVar large enough for the
+ * requested number of logical digits plus one spare leading digit.
+ *
+ * The variable is left in an owned writable state with basic working metadata
+ * initialized, but callers remain responsible for constructing the final
+ * numeric value.
  */
 static void
 alloc_var(NumericVar *var, int ndigits)
 {
-	digitbuf_free(var->buf);
-	var->buf = digitbuf_alloc(ndigits + 1);
-	var->buf[0] = 0;			/* spare digit for rounding */
-	var->digits = var->buf + 1;
+	digitbuf_free(var);
+
+	digitbuf_get(var, ndigits + 1);
+
 	var->ndigits = ndigits;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
+	var->buf[0] = 0;			/* ensure spare digit is zeroed */
 }
 
 
 /*
  * free_var() -
  *
- *	Return the digit buffer of a variable to the free pool
+ * Release the current active storage of a NumericVar and reset its numeric
+ * metadata to an empty working state.
+ *
+ * Unlike digitbuf_free(), this is a variable-level cleanup helper: it clears
+ * both storage-state and value-level fields.
  */
 static void
 free_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
-	var->buf = NULL;
-	var->digits = NULL;
-	var->sign = NUMERIC_NAN;
+	/* Release current active storage, if any. */
+	digitbuf_free(var);
+
+	var->ndigits = 0;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
 }
 
 
@@ -6744,7 +7278,7 @@ free_var(NumericVar *var)
 static void
 zero_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
+	free_var(var);
 	var->buf = NULL;
 	var->digits = NULL;
 	var->ndigits = 0;
@@ -6923,9 +7457,11 @@ set_var_from_str(const char *str, const char *cp,
 	ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
 	dest->sign = sign;
 	dest->weight = weight;
 	dest->dscale = dscale;
+	dest->digits = dest->buf + 1;
 
 	i = DEC_DIGITS - offset;
 	digits = dest->digits;
@@ -7184,6 +7720,8 @@ set_var_from_num(Numeric num, NumericVar *dest)
 	ndigits = NUMERIC_NDIGITS(num);
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
+	dest->digits = dest->buf + 1;
 
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
@@ -7196,50 +7734,188 @@ set_var_from_num(Numeric num, NumericVar *dest)
 /*
  * init_var_from_num() -
  *
- *	Initialize a variable from packed db format. The digits array is not
- *	copied, which saves some cycles when the resulting var is not modified.
- *	Also, there's no need to call free_var(), as long as you don't assign any
- *	other value to it (with set_var_* functions, or by using the var as the
- *	destination of a function like add_var())
+ * Initialize a NumericVar from an input Numeric datum, detoasting the datum
+ * first if necessary.
  *
- *	CAUTION: Do not modify the digits buffer of a var initialized with this
- *	function, e.g by calling round_var() or trunc_var(), as the changes will
- *	propagate to the original Numeric! It's OK to use it as the destination
- *	argument of one of the calculational functions, though.
+ * For finite values, the resulting NumericVar is a borrowed read-only view of
+ * externally owned digit storage, with borrow kind
+ * NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the detoasted representation actually used
+ * to initialize the borrowed view.
  */
-static void
+static Numeric
 init_var_from_num(Numeric num, NumericVar *dest)
 {
+	/* ensure the numeric is always detoasted */
+	num = (Numeric) PG_DETOAST_DATUM(NumericGetDatum(num));
+
+	init_var(dest);
+
 	dest->ndigits = NUMERIC_NDIGITS(num);
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
 	dest->dscale = NUMERIC_DSCALE(num);
-	dest->digits = NUMERIC_DIGITS(num);
-	dest->buf = NULL;			/* digits array is not palloc'd */
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->state = NUMVAR_EMPTY;
+		dest->borrow_kind = NUMVAR_BORROW_NONE;
+	}
+	else
+	{
+		dest->state = NUMVAR_BORROWED;
+		dest->borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+		dest->digits = NUMERIC_DIGITS(num);
+	}
+
+	return num;
+}
+
+
+/*
+ * init_buffered_var_from_num() -
+ *
+ * Initialize a BufferedNumericVar from an input Numeric datum, using the
+ * object's inline buffer as detoast workspace when possible.
+ *
+ * For finite values, the resulting NumericVar is a borrowed read-only view.
+ * If the detoasted representation is materialized in the inline buffer, the
+ * borrow kind is recorded as NUMVAR_BORROW_INLINE; otherwise it is recorded
+ * as NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the representation actually used to
+ * initialize the borrowed view.
+ */
+static Numeric
+init_buffered_var_from_num(Numeric num, BufferedNumericVar * dest)
+{
+	num = (Numeric) numeric_detoast_attr_to_buffer((varlena *) (num),
+												   (unsigned char *) dest->buffer,
+												   sizeof(dest->buffer));
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+		init_var(&dest->var);
+		dest->var.inline_buf = dest->buffer;
+		dest->var.sign = NUMERIC_SIGN(num);
+		return num;
+	}
+
+	dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&dest->var);
+	dest->var.inline_buf = dest->buffer;
+
+	dest->var.state = NUMVAR_BORROWED;
+	dest->var.ndigits = NUMERIC_NDIGITS(num);
+	dest->var.weight = NUMERIC_WEIGHT(num);
+	dest->var.sign = NUMERIC_SIGN(num);
+	dest->var.dscale = NUMERIC_DSCALE(num);
+	dest->var.digits = NUMERIC_DIGITS(num);
+
+	if ((unsigned char *) num == (unsigned char *) dest->buffer)
+		dest->var.borrow_kind = NUMVAR_BORROW_INLINE;
+	else
+		dest->var.borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+	return num;
+}
+
+
+/*
+ * numeric_detoast_attr_to_buffer() -
+ *
+ * Convert a varlena datum to a detoasted representation, preferably using a
+ * caller-provided buffer when that is sufficient.
+ *
+ * This helper is intended for numeric paths.  If the input datum is in
+ * short-header form, it is expanded to ordinary 4-byte-header form, using
+ * the supplied buffer when possible and falling back to palloc'd storage
+ * otherwise.
+ *
+ * If the input datum is not in short-header form, this helper falls back to
+ * detoast_attr().
+ *
+ * The returned pointer may therefore refer either to the caller-provided
+ * buffer or to freshly allocated storage.
+ */
+static varlena *
+numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+							   size_t buffer_size)
+{
+	Size		data_size;
+	Size		new_size;
+	varlena    *new_attr;
+
+	if (!VARATT_IS_SHORT(attr))
+		return pg_detoast_datum(attr);
+
+	/*
+	 * This is a short-header varlena --- convert to 4-byte header format
+	 */
+	data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
+	new_size = data_size + VARHDRSZ;
+
+	if (unlikely(buffer_size < new_size))
+		new_attr = (varlena *) palloc(new_size);
+	else
+		new_attr = (varlena *) buffer;
+	SET_VARSIZE(new_attr, new_size);
+	memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
+	attr = new_attr;
+
+	return attr;
 }
 
 
 /*
  * set_var_from_var() -
  *
- *	Copy one variable into another
+ * Copy one NumericVar into another.
+ *
+ * The destination receives the same numeric value as the source, but this
+ * function always constructs a destination representation that is valid for
+ * the destination's own storage state.  It does not steal storage from the
+ * source.
+ *
+ * This function must remain safe when source and destination are the same
+ * variable.
  */
 static void
 set_var_from_var(const NumericVar *value, NumericVar *dest)
 {
-	NumericDigit *newbuf;
+	if (value == dest)
+		return;
 
-	newbuf = digitbuf_alloc(value->ndigits + 1);
-	newbuf[0] = 0;				/* spare digit for rounding */
-	if (value->ndigits > 0)		/* else value->digits might be null */
-		memcpy(newbuf + 1, value->digits,
-			   value->ndigits * sizeof(NumericDigit));
+	if (NUMERICVAR_IS_SPECIAL(value))
+	{
+		free_var(dest);
+		dest->sign = value->sign;
+
+		return;
+	}
 
-	digitbuf_free(dest->buf);
+	/* ndigits is set in alloc_var */
+	alloc_var(dest, value->ndigits);
 
-	memmove(dest, value, sizeof(NumericVar));
-	dest->buf = newbuf;
-	dest->digits = newbuf + 1;
+	dest->weight = value->weight;
+	dest->dscale = value->dscale;
+	dest->sign = value->sign;
+
+	if (value->ndigits > 0)
+		memcpy(dest->digits,
+			value->digits,
+			value->ndigits * sizeof(NumericDigit));
 }
 
 
@@ -8600,6 +9276,7 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
 	 */
 	result->weight = res_weight;
 	result->sign = res_sign;
+	result->ndigits = res_ndigits;
 
 	/* Round to target rscale (and set result->dscale) */
 	round_var(result, rscale);
@@ -8631,6 +9308,8 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	NumericDigit *res_digits;
 	uint32		carry = 0;
 	uint32		term;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Check preconditions */
 	Assert(var1ndigits >= 1);
@@ -8651,8 +9330,9 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	res_weight = var1->weight + var2->weight + 1;
 	res_ndigits = var1ndigits + var2ndigits;
 
-	/* Allocate result digit array */
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -8872,16 +9552,17 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	}
 
 	/* Store the product in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
-	result->dscale = var1->dscale + var2->dscale;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = var1->dscale + var2->dscale;
 
 	/* Strip leading and trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -9457,6 +10138,8 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint32		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9490,7 +10173,9 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9532,12 +10217,14 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -9573,6 +10260,8 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint64		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9606,7 +10295,9 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9648,12 +10339,13 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -11494,6 +12186,8 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			carry = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11514,7 +12208,9 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11543,15 +12239,15 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(carry == 0);			/* else we failed to allow for carry out */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -11579,6 +12275,8 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			borrow = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11599,7 +12297,9 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11628,15 +12328,15 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(borrow == 0);		/* else caller gave us var1 < var2 */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 /*
@@ -12099,7 +12799,9 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 
 	/* Create NumericVars representing the positive and negative sums */
 	init_var(&pos_var);
+	alloc_var(&pos_var, accum->ndigits);
 	init_var(&neg_var);
+	alloc_var(&neg_var, accum->ndigits);
 
 	pos_var.ndigits = neg_var.ndigits = accum->ndigits;
 	pos_var.weight = neg_var.weight = accum->weight;
@@ -12107,8 +12809,8 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 	pos_var.sign = NUMERIC_POS;
 	neg_var.sign = NUMERIC_NEG;
 
-	pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits);
-	neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits);
+	pos_var.buf = pos_var.digits = digitbuf_alloc_raw(accum->ndigits);
+	neg_var.buf = neg_var.digits = digitbuf_alloc_raw(accum->ndigits);
 
 	for (i = 0; i < accum->ndigits; i++)
 	{
-- 
2.34.1



  [application/octet-stream] 0002-numeric-add-regression-coverage-for-NumericVar-state.patch (132.3K, ../../[email protected]/4-0002-numeric-add-regression-coverage-for-NumericVar-state.patch)
  download | inline diff:
From c48140b9368cb3c0ce6f2e9ade1e5989d3171a4c Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:43:50 +0800
Subject: [PATCH 2/2] numeric: add regression coverage for NumericVar state
 changes

Add regression coverage for the NumericVar storage-state refactoring.

The tests cover a wide range of numeric typmods, including high-precision
and high-scale cases, and exercise arithmetic, composite expressions,
comparison-sensitive paths, window functions, join-driven expressions,
aggregates, updates, error cases, and index/index-only related paths.

This is intended to verify that the refactoring preserves SQL-visible
behavior across both ordinary and extreme numeric workloads.
---
 src/test/regress/expected/numeric_op_big.out  | 497 ++++++++++++++
 src/test/regress/expected/numeric_op_ddl.out  | 167 +++++
 .../regress/expected/numeric_op_medium.out    | 497 ++++++++++++++
 .../regress/expected/numeric_op_small.out     | 634 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   4 +
 src/test/regress/sql/numeric_op_big.sql       | 267 ++++++++
 src/test/regress/sql/numeric_op_ddl.sql       | 173 +++++
 src/test/regress/sql/numeric_op_medium.sql    | 267 ++++++++
 src/test/regress/sql/numeric_op_small.sql     | 333 +++++++++
 9 files changed, 2839 insertions(+)
 create mode 100644 src/test/regress/expected/numeric_op_big.out
 create mode 100644 src/test/regress/expected/numeric_op_ddl.out
 create mode 100644 src/test/regress/expected/numeric_op_medium.out
 create mode 100644 src/test/regress/expected/numeric_op_small.out
 create mode 100644 src/test/regress/sql/numeric_op_big.sql
 create mode 100644 src/test/regress/sql/numeric_op_ddl.sql
 create mode 100644 src/test/regress/sql/numeric_op_medium.sql
 create mode 100644 src/test/regress/sql/numeric_op_small.sql

diff --git a/src/test/regress/expected/numeric_op_big.out b/src/test/regress/expected/numeric_op_big.out
new file mode 100644
index 00000000000..12c3cf65a83
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_big.out
@@ -0,0 +1,497 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -93674011458
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 49431437020
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | 81596504540
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum      |     sum     |     sum     |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+-------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 103787738890 | 71241136711 | 91238480326 | -25880446966 | 85861504452 | 138111163668 | -64934216831 | -3883271108300 | -3883271108300 | -7965490469700 | -263811485216 | -196222417055 | -253470347100
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+      sum      |     sum      |     sum     |     sum      |      sum      |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+---------------+--------------+-------------+--------------+---------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ -168835944407 | -37026039983 | -9162875272 | 103006726746 | -108905948664 | -77309522276 | 146591765000 | -3883271108300 | -3883271108300 | -7965490469700 | -111504876460 | -183625584196 | -243750883157
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |     sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+--------------+--------------
+ 81596504540 | 81596504540 | -448199909000 |     |     | -448199909000 | 81596504540 | 153330623000 | -1995205883000 | -1995205883000 | 81596504540 | -70370694025 | -81922915700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -153072055232
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -221944827430
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ -87303812940 | -87303812940 | -36200000898
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_800
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_800
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_995
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_995
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_1000
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_1000
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 187915496731
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |      sum      
+-------+---------------
+  2001 | -120277609243
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | -6791326615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum     |     sum      |     sum     |    sum     |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum      |     sum      |     sum      
+-------------+--------------+-------------+------------+-------------+-------------+-------------+----------------+----------------+----------------+--------------+--------------+--------------
+ 69378299805 | -28004625396 | 69165702075 | 6495822165 | 42794676167 | 28778131261 | 23748489343 | -3668417457700 | -3883271108300 | -8180344120300 | 162788189344 | -88035415965 | -14300392972
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+     sum      |    sum    |     sum      |     sum      |     sum      |    sum     |     sum      |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+-----------+--------------+--------------+--------------+------------+--------------+----------------+----------------+----------------+---------------+--------------+---------------
+ 106142378501 | 456865251 | -40344563826 | -82057607022 | -74296237953 | 4474319379 | 111919427572 | -3668417457700 | -3883271108300 | -8180344120300 | -223137274026 | -60159164731 | -114299102966
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |      sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+---------------+--------------
+ -6791326615 | -6791326615 | -448199909000 |     |     | -448199909000 | -6791326615 | 153330623000 | -1995205883000 | -1995205883000 | -6791326615 | -116888674645 | -70370694025
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -23880525307
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 31543863976
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ -87303812940 | -87303812940 | 18056325685
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+     sum      
+--------------
+ -27571036926
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+     sum      
+--------------
+ 142042826843
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
+     sum      
+--------------
+ -37293091674
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_ddl.out b/src/test/regress/expected/numeric_op_ddl.out
new file mode 100644
index 00000000000..d8a1523a5fc
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_ddl.out
@@ -0,0 +1,167 @@
+DROP TABLE IF EXISTS t_9_2;
+NOTICE:  table "t_9_2" does not exist, skipping
+DROP TABLE IF EXISTS t_18_2;
+NOTICE:  table "t_18_2" does not exist, skipping
+DROP TABLE IF EXISTS t_38_2;
+NOTICE:  table "t_38_2" does not exist, skipping
+DROP TABLE IF EXISTS t_80_16;
+NOTICE:  table "t_80_16" does not exist, skipping
+DROP TABLE IF EXISTS t_175_35;
+NOTICE:  table "t_175_35" does not exist, skipping
+DROP TABLE IF EXISTS t_400_80;
+NOTICE:  table "t_400_80" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_200;
+NOTICE:  table "t_1000_200" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_800;
+NOTICE:  table "t_1000_800" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_995;
+NOTICE:  table "t_1000_995" does not exist, skipping
+DROP TABLE IF EXISTS t_base;
+NOTICE:  table "t_base" does not exist, skipping
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+DROP TABLE IF EXISTS t_1000_1000;
+NOTICE:  table "t_1000_1000" does not exist, skipping
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+VACUUM FREEZE ANALYZE t_1000_1000;
diff --git a/src/test/regress/expected/numeric_op_medium.out b/src/test/regress/expected/numeric_op_medium.out
new file mode 100644
index 00000000000..13a0f568726
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_medium.out
@@ -0,0 +1,497 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -41380769803
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -17147548455
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -87880682140
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum      |     sum      |     sum     |     sum     |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+--------------+-------------+-------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 328096480219 | 106894971180 | 29998131312 | 16824177090 | -98601154544 | -84446300842 | -142367027518 | -3883271108300 | -3883271108300 | -7965490469700 | -31882620402 | -214909270261 | -312201369170
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |      sum      |     sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+---------------+--------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+---------------+--------------+---------------
+ -89713139799 | -218213819552 | 100513273638 | -79355358716 | 45044419840 | -3009527768 | 27142855016 | -3883271108300 | -3883271108300 | -7965490469700 | -248734189206 | -85072914373 | -210488126609
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+    sum    |     sum      |     sum      |     sum      |     sum      |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+-----------+--------------+--------------+--------------+--------------+-------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 534102535 | 222800148124 | 252518296200 | -40723661604 | -53446320231 | 49858073732 | 23224917403 | -3883271108300 | -3883271108300 | -7965490469700 | -212735400480 | -257086925922 | -206858526458
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 73062391064
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | 5986675256
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -10341341254
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_175_35
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_175_35
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_400_80
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_400_80
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: a
+   ->  Bitmap Heap Scan on t_1000_200
+         Recheck Cond: (a > '1000'::numeric)
+         ->  Bitmap Index Scan on idx_t_1000_200
+               Index Cond: (a > '1000'::numeric)
+(6 rows)
+
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -89558437612
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -19722210906
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 277783880994
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum     |     sum      |     sum      |     sum     |      sum      |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |     sum      
+-------------+--------------+--------------+-------------+---------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+--------------
+ 48377864776 | -49822309848 | 178050220784 | 16824177090 | -138513601794 | 194342415281 | 28892192658 | -3668417457700 | -3883271108300 | -8180344120300 | -151559852522 | -152028559294 | -63268316355
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |     sum      |     sum      |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |     sum      |     sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+--------------+--------------+---------------
+ -67452832312 | -89225471931 | 146580491810 | -79355358716 | -9287051683 | 147260222842 | -70335569319 | -3668417457700 | -3883271108300 | -8180344120300 | -32938146279 | -59204444070 | -108234520264
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+      sum      |      sum      |    sum    |     sum      |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+-----------+--------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -163275209177 | -193968650050 | 679620265 | -40723661604 | -40601882101 | 147411738034 | -193122653902 | -3668417457700 | -3883271108300 | -8180344120300 | 270045436866 | -108867098352 | -172351504959
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -35696475290
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -203092149118
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -75070192124
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+     sum      
+--------------
+ -27666707523
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+     sum     
+-------------
+ 68424688419
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
+     sum      
+--------------
+ -58373747124
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_small.out b/src/test/regress/expected/numeric_op_small.out
new file mode 100644
index 00000000000..1b124ab3dbb
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_small.out
@@ -0,0 +1,634 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 25264177945
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -18318735365
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 77086657878
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 51102838669
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+     sum      |     sum     |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 185332843643 | 74295396054 | -265653763404 | -130951372896 | 33926686039 | -93220527164 | 53982021999 | -3883271108300 | -3883271108300 | -7965490469700 | -136748774422 | -156349489824 | -229516261618
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+     sum      |    sum     |     sum      |      sum      |      sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+------------+--------------+---------------+---------------+---------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 205522402955 | 1737491244 | -98734378124 | -156012333362 | -122318628351 | -265348319222 | 53131151046 | -3883271108300 | -3883271108300 | -7965490469700 | -74716919418 | -132260756970 | -267366558171
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+     sum      |     sum      |      sum      |      sum      |     sum      |     sum      |     sum      |      sum       |      sum       |      sum       |    sum     |      sum      |      sum      
+--------------+--------------+---------------+---------------+--------------+--------------+--------------+----------------+----------------+----------------+------------+---------------+---------------
+ 188179625901 | 112456755410 | -343776078076 | -174825681708 | -71003178193 | 142439459768 | 114154703061 | -3883271108300 | -3883271108300 | -7965490469700 | 8740136096 | -345863766775 | -368207427216
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum     |      sum      |     sum      |      sum      |      sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+-------------+---------------+--------------+---------------+---------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 159659505470 | -1983944819 | -319417093044 | -11104453418 | -158100462876 | -145951162894 | -124997818319 | -3883271108300 | -3883271108300 | -7965490469700 | 146077391644 | -208253897723 | -211323905998
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 47811429266
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -17989105700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -79591213532
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -190878347020
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+             QUERY PLAN              
+-------------------------------------
+ Index Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+               QUERY PLAN                
+-----------------------------------------
+ Index Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 55192112654
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 79276791029
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 119235302735
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 109095039015
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+      sum      |      sum      |      sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum     |     sum      |     sum      
+---------------+---------------+---------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+-------------+--------------+--------------
+ -198398285973 | -268636739265 | -185796812937 | -80212568293 | 40366812062 | 50022231011 | 86359907719 | -3668417457700 | -3883271108300 | -8180344120300 | 88627277998 | -83578376254 | -56047555653
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+      sum      |      sum      |      sum      |     sum      |     sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+---------------+---------------+---------------+--------------+--------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ -198076509004 | -185931021498 | -174261325669 | -62721548562 | -87570938488 | -151149981174 | 46905358609 | -3668417457700 | -3883271108300 | -8180344120300 | 66791978874 | -162795673535 | -140859793615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+      sum      |      sum      |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -173598401621 | -191141226891 | -153991008984 | -174825681708 | -9994590731 | 103039770436 | 90917337737 | -3668417457700 | -3883271108300 | -8180344120300 | 212488760840 | -157402192456 | -222636044392
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum      |     sum      |     sum      |     sum     |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ 179235436643 | 248686171421 | -90086495035 | -11104453418 | 11551494962 | -229139601272 | 32577271698 | -3668417457700 | -3883271108300 | -8180344120300 | 81696122255 | -125106925825 | -150252904102
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -13202507172
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | -696258940
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 19679287493
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | -6889081579
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+     sum      
+--------------
+ -16440552090
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+     sum      
+--------------
+ 104998257177
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+    sum     
+------------
+ 9189839531
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
+      sum      
+---------------
+ -126456520242
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..926010b47cb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -146,3 +146,7 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# this test is to check if numeric operations are correct
+test: numeric_op_ddl
+test: numeric_op_small numeric_op_medium numeric_op_big
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_big.sql b/src/test/regress/sql/numeric_op_big.sql
new file mode 100644
index 00000000000..eb01dfd2091
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_big.sql
@@ -0,0 +1,267 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+reset enable_seqscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_ddl.sql b/src/test/regress/sql/numeric_op_ddl.sql
new file mode 100644
index 00000000000..5d213c75acd
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_ddl.sql
@@ -0,0 +1,173 @@
+DROP TABLE IF EXISTS t_9_2;
+DROP TABLE IF EXISTS t_18_2;
+DROP TABLE IF EXISTS t_38_2;
+DROP TABLE IF EXISTS t_80_16;
+DROP TABLE IF EXISTS t_175_35;
+DROP TABLE IF EXISTS t_400_80;
+DROP TABLE IF EXISTS t_1000_200;
+DROP TABLE IF EXISTS t_1000_800;
+DROP TABLE IF EXISTS t_1000_995;
+
+DROP TABLE IF EXISTS t_base;
+
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+
+DROP TABLE IF EXISTS t_1000_1000;
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_1000_1000;
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_medium.sql b/src/test/regress/sql/numeric_op_medium.sql
new file mode 100644
index 00000000000..dd3802e79c1
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_medium.sql
@@ -0,0 +1,267 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+reset enable_seqscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_small.sql b/src/test/regress/sql/numeric_op_small.sql
new file mode 100644
index 00000000000..c90537d6151
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_small.sql
@@ -0,0 +1,333 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+reset enable_seqscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: [PATCH v3] Make NumericVar storage semantics explicit
@ 2026-05-16 04:37  =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  parent: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: =?utf-8?B?Q2hlbmh1aU1v?= @ 2026-05-16 04:37 UTC (permalink / raw)
  To: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>; pgsql-hackers

Hi&nbsp;all,


While rechecking the series, I found a test instability and fixed it in the attached refresh.


Thanks!


Regards,
Chenhui Mo

Attachments:

  [application/octet-stream] 0001-numeric-make-NumericVar-storage-states-explicit-and-.patch (56.2K, ../../[email protected]/3-0001-numeric-make-NumericVar-storage-states-explicit-and-.patch)
  download | inline diff:
From 45495e199eb456ad44f2553f9d0fcac0e18d8964 Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:42:17 +0800
Subject: [PATCH 1/2] numeric: make NumericVar storage states explicit and use
 them in core paths

Clarify the in-memory storage model of NumericVar by making buffer
ownership and mutability states explicit, and reorganize the related
helper layer around that model.

This introduces explicit storage states for empty, borrowed,
owned-inline, and owned-heap representations, together with
borrow-kind tracking for borrowed digit views. It also adds
BufferedNumericVar as the inline-home wrapper used by the new model.

The patch then connects the new helper semantics to core comparison,
hashing, and basic arithmetic paths, while keeping higher-level numeric
algorithms structurally close to the existing implementation.

The goal is not to alter SQL-visible numeric behavior, but to make the
existing internal contract around NumericVar storage easier to
understand, document, and maintain.
---
 src/backend/utils/adt/numeric.c | 1197 ++++++++++++++++++++++++-------
 1 file changed, 949 insertions(+), 248 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..f85abf12881 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -104,6 +104,7 @@ typedef int16 NumericDigit;
 #endif
 
 #define NBASE_SQR	(NBASE * NBASE)
+#define NUMERIC_INLINE_BUFFER_SIZE 128
 
 /*
  * The Numeric type as stored on disk.
@@ -261,66 +262,165 @@ struct NumericData
 #define NUMERIC_WEIGHT_MAX			PG_INT16_MAX
 
 /* ----------
- * NumericVar is the format we use for arithmetic.  The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array.  If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight.  Another way to say it is that there are weight+1
- * digits before the decimal point.  It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar.  digits points at the first digit in actual use (the one
- * with the specified weight).  We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space.  We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present.  NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
+ * NumericVar is the in-memory working representation of a numeric value.
+ *
+ * A NumericVar always stores the logical numeric metadata (ndigits, weight,
+ * sign, dscale), but the physical storage backing digits[] is stateful.
+ *
+ * There are four storage states:
+ *
+ * 1. NUMVAR_EMPTY
+ *    The variable carries no digits storage and no digit payload.
+ *
+ * 2. NUMVAR_BORROWED
+ *    digits points to storage owned by some other representation.
+ *    In this state the current NumericVar does not own writable digit
+ *    storage, and buf is NULL.
+ *
+ *    Borrowed storage is further classified by borrow_kind:
+ *
+ *    - NUMVAR_BORROW_EXTERNAL
+ *      digits points into external numeric storage, such as a detoasted
+ *      Numeric datum.  This storage is read-only from the NumericVar's
+ *      perspective and cannot be upgraded in-place to writable inline
+ *      storage.
+ *
+ *    - NUMVAR_BORROW_INLINE
+ *      digits points into numeric storage currently materialized inside the
+ *      inline buffer of an associated BufferedNumericVar.  This is still a
+ *      read-only borrowed view, but because the underlying memory belongs to
+ *      the same buffered home, it may later be converted into writable inline
+ *      working storage.
+ *
+ * 3. NUMVAR_OWNED_INLINE
+ *    buf points to the inline buffer associated with this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * 4. NUMVAR_OWNED_HEAP
+ *    buf points to palloc'd writable storage owned by this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * In owned states, buf is the base of the current writable digit storage and
+ * digits points to the logical first digit.  As in the historical NumericVar
+ * representation, owned storage is initially allocated with a spare leading
+ * digit so that carry propagation can enlarge the value without immediate
+ * reallocation.  Stored numeric datums do not have this extra working space;
+ * it exists only in the in-memory working representation.  Therefore, a
+ * borrowed view of a stored/detoasted Numeric datum must not be treated as a
+ * writable work buffer without an explicit state transition.
+ *
+ * Note, however, that digits is not required to remain equal to buf + 1 for
+ * the lifetime of an owned NumericVar.  Subsequent normalization steps such as
+ * stripping leading zeroes may advance digits within the owned buffer, as long
+ * as the active digit range remains within the storage owned by buf.
+ *
+ * inline_buf, when non-NULL, identifies the fixed-size inline home provided by
+ * BufferedNumericVar.  It does not by itself imply that the variable is
+ * currently in NUMVAR_OWNED_INLINE state.  For example, a NumericVar may be in
+ * NUMVAR_BORROWED or NUMVAR_OWNED_HEAP state while still retaining an inline
+ * home for possible future reuse.
+ *
+ * capacity is the physical capacity of the current owned buffer, including the
+ * spare leading digit.  It is meaningful only in owned states, and is zero in
+ * empty or borrowed states.
+ *
+ * As with the traditional implementation, variable-level functions should be
+ * written so that a NumericVar may safely be used as both input and
+ * destination.  State transitions must preserve that property.
+ *
+ * NumericVar state invariants:
+ *
+ * NUMVAR_EMPTY:
+ *     buf == NULL
+ *     digits == NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *
+ * NUMVAR_BORROWED:
+ *     buf == NULL
+ *     borrow_kind != NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *     digits points to borrowed storage, unless sign is special
+ *
+ * NUMVAR_OWNED_INLINE:
+ *     buf == inline_buf
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == NUMERIC_INLINE_BUFFER_SIZE
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * NUMVAR_OWNED_HEAP:
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity > 0
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * Special values are represented through sign, not through a separate storage
+ * state.  For NaN and infinities, only sign is semantically meaningful.
  * ----------
  */
+
+typedef enum NUMERIC_VAR_STATUS
+{
+	NUMVAR_EMPTY,
+	NUMVAR_BORROWED,
+	NUMVAR_OWNED_INLINE,
+	NUMVAR_OWNED_HEAP
+}			NUMERIC_VAR_STATUS;
+
+typedef enum NUMERIC_BORROW_KIND
+{
+	NUMVAR_BORROW_NONE,
+	NUMVAR_BORROW_EXTERNAL,
+	NUMVAR_BORROW_INLINE
+}			NUMERIC_BORROW_KIND;
+
 typedef struct NumericVar
 {
-	int			ndigits;		/* # of digits in digits[] - can be 0! */
+	/* logical numeric metadata */
+	int			ndigits;		/* # of digits in digits[]; may be 0 */
 	int			weight;			/* weight of first digit */
 	int			sign;			/* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
 	int			dscale;			/* display scale */
-	NumericDigit *buf;			/* start of palloc'd space for digits[] */
-	NumericDigit *digits;		/* base-NBASE digits */
+
+	/* storage state metadata */
+	NUMERIC_VAR_STATUS state;	/* current storage state */
+	NUMERIC_BORROW_KIND borrow_kind;	/* source of borrowed digits, if
+										 * borrowed */
+	int			capacity;		/* current owned buffer capacity, spare digit
+								 * included */
+
+	/* current digit storage */
+	NumericDigit *buf;			/* base of current owned writable storage, or
+								 * NULL */
+	NumericDigit *digits;		/* logical first base-NBASE digit, or NULL */
+
+	/* optional fixed-size inline home */
+	NumericDigit *inline_buf;	/* inline buffer of associated
+								 * BufferedNumericVar, or NULL */
 } NumericVar;
 
+#define NUMERIC_VARLENA_SIZE (VARHDRSZ / sizeof(NumericDigit) + \
+	(sizeof(uint16) + sizeof(int16)) / sizeof(NumericDigit) + \
+	NUMERIC_INLINE_BUFFER_SIZE + DIV_GUARD_DIGITS + 4)
+
+typedef struct BufferedNumericVar
+{
+	NumericVar	var;
+	int64		magic;			/* used for align with 8-byte */
+	NumericDigit buffer[NUMERIC_VARLENA_SIZE];
+}			BufferedNumericVar;
+
+
+#define BUFFERED_NUMERIC_VAR_MAGIC 0x4E554D455249435F	/* 'NUMERIC_MAGIC' */
+
+#define NUMERICVAR_IS_SPECIAL(v) \
+    ((v)->sign == NUMERIC_NAN || (v)->sign == NUMERIC_PINF || (v)->sign == NUMERIC_NINF)
+
 
 /* ----------
  * Data for generate_series
@@ -415,18 +515,18 @@ typedef struct NumericSumAccum
  */
 static const NumericDigit const_zero_data[1] = {0};
 static const NumericVar const_zero =
-{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data};
+{0, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_data, NULL};
 
 static const NumericDigit const_one_data[1] = {1};
 static const NumericVar const_one =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericVar const_minus_one =
-{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_NEG, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericDigit const_two_data[1] = {2};
 static const NumericVar const_two =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_two_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_zero_point_nine_data[1] = {9000};
@@ -436,7 +536,7 @@ static const NumericDigit const_zero_point_nine_data[1] = {90};
 static const NumericDigit const_zero_point_nine_data[1] = {9};
 #endif
 static const NumericVar const_zero_point_nine =
-{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data};
+{1, -1, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_point_nine_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_one_point_one_data[2] = {1, 1000};
@@ -446,28 +546,31 @@ static const NumericDigit const_one_point_one_data[2] = {1, 10};
 static const NumericDigit const_one_point_one_data[2] = {1, 1};
 #endif
 static const NumericVar const_one_point_one =
-{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data};
+{2, 0, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_point_one_data, NULL};
 
 static const NumericVar const_nan =
-{0, 0, NUMERIC_NAN, 0, NULL, NULL};
+{0, 0, NUMERIC_NAN, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_pinf =
-{0, 0, NUMERIC_PINF, 0, NULL, NULL};
+{0, 0, NUMERIC_PINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_ninf =
-{0, 0, NUMERIC_NINF, 0, NULL, NULL};
+{0, 0, NUMERIC_NINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 #if DEC_DIGITS == 4
 static const int round_powers[4] = {0, 1000, 100, 10};
 #endif
 
-
 /* ----------
  * Local functions
  * ----------
  */
 
 #ifdef NUMERIC_DEBUG
+static inline int check_digit_overflow(const NumericVar *var);
+static inline void check_numericvar_state(const NumericVar *var, const char *where);
+#define CHECK_NUMERICVAR(v) check_numericvar_state((v), __func__)
+#define CHECK_NUMERICVAR_AT(v, where) check_numericvar_state((v), (where))
 static void dump_numeric(const char *str, Numeric num);
 static void dump_var(const char *str, NumericVar *var);
 #else
@@ -475,15 +578,65 @@ static void dump_var(const char *str, NumericVar *var);
 #define dump_var(s,v)
 #endif
 
-#define digitbuf_alloc(ndigits)  \
-	((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit)))
-#define digitbuf_free(buf)	\
-	do { \
-		 if ((buf) != NULL) \
-			 pfree(buf); \
-	} while (0)
+/*
+ * digitbuf_alloc_raw() -
+ *
+ * Allocate raw heap-backed digit storage with the specified physical
+ * capacity.
+ *
+ * The capacity is measured in NumericDigit units and includes any spare
+ * leading digit space required by the caller.
+ *
+ * This is a low-level storage primitive.  It does not inspect or update any
+ * NumericVar state, and it does not by itself establish a complete working
+ * NumericVar representation.
+ */
+static inline NumericDigit *
+digitbuf_alloc_raw(int sz)
+{
+	return (NumericDigit *) palloc(sizeof(NumericDigit) * sz);
+}
 
-#define init_var(v)		memset(v, 0, sizeof(NumericVar))
+/*
+ * init_var() -
+ *
+ * Initialize a NumericVar in NUMVAR_EMPTY storage state, with no active digit
+ * storage, no borrowed digit view, and no current digit payload.
+ *
+ * This establishes a clean starting point for later transitions into
+ * borrowed or owned states.
+ */
+static inline void
+init_var(NumericVar *v)
+{
+	memset(v, 0, sizeof(NumericVar));
+}
+
+/*
+ * init_buffered_var() -
+ *
+ * Initialize a BufferedNumericVar so that its embedded NumericVar starts with
+ * writable inline working storage backed by the object's fixed-size inline
+ * buffer.
+ *
+ * The inline buffer is installed as the current owned storage, and the digit
+ * pointer is initialized to the normal working position after the spare
+ * leading digit.
+ */
+static void
+init_buffered_var(BufferedNumericVar * bvar)
+{
+	bvar->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&bvar->var);
+
+	bvar->var.inline_buf = bvar->buffer;
+	bvar->var.capacity = NUMERIC_INLINE_BUFFER_SIZE;
+	bvar->var.state = NUMVAR_OWNED_INLINE;
+	bvar->var.borrow_kind = NUMVAR_BORROW_NONE;
+	bvar->var.buf = bvar->buffer;
+	bvar->var.buf[0] = 0;
+	bvar->var.digits = bvar->buffer + 1;
+}
 
 #define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
 	(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
@@ -494,8 +647,27 @@ static void dump_var(const char *str, NumericVar *var);
 	(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
 	(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
 
+static void digitbuf_free(NumericVar *var);
+
+static NumericDigit *digitbuf_get(NumericVar *var,
+								  int require_ndigits);
+
 static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
+
+static bool result_aliases_input(const NumericVar *result,
+								 const NumericVar *a,
+								 const NumericVar *b);
+
+static NumericVar *prepare_result_var(NumericVar *result,
+									  const NumericVar *a,
+									  const NumericVar *b,
+									  int res_ndigits,
+									  BufferedNumericVar * tmp);
+
+static void finish_result_var(NumericVar *work_result,
+							  NumericVar *result);
+
 static void zero_var(NumericVar *var);
 
 static bool set_var_from_str(const char *str, const char *cp,
@@ -507,7 +679,10 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
 												 const char **endptr,
 												 Node *escontext);
 static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_buffered_var_from_num(Numeric src, BufferedNumericVar * bvar);
+static varlena *numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+											   size_t buffer_size);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
 static char *get_str_from_var(const NumericVar *var);
 static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -543,6 +718,7 @@ static int	cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
 						   int var1weight, int var1sign,
 						   const NumericDigit *var2digits, int var2ndigits,
 						   int var2weight, int var2sign);
+
 static void add_var(const NumericVar *var1, const NumericVar *var2,
 					NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -2416,8 +2592,8 @@ numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
 Datum
 numeric_cmp(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	int			result;
 
 	result = cmp_numerics(num1, num2);
@@ -2428,12 +2604,11 @@ numeric_cmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
-
 Datum
 numeric_eq(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) == 0;
@@ -2447,8 +2622,8 @@ numeric_eq(PG_FUNCTION_ARGS)
 Datum
 numeric_ne(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) != 0;
@@ -2462,8 +2637,8 @@ numeric_ne(PG_FUNCTION_ARGS)
 Datum
 numeric_gt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) > 0;
@@ -2477,8 +2652,8 @@ numeric_gt(PG_FUNCTION_ARGS)
 Datum
 numeric_ge(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) >= 0;
@@ -2492,8 +2667,8 @@ numeric_ge(PG_FUNCTION_ARGS)
 Datum
 numeric_lt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) < 0;
@@ -2507,8 +2682,8 @@ numeric_lt(PG_FUNCTION_ARGS)
 Datum
 numeric_le(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) <= 0;
@@ -2523,6 +2698,11 @@ static int
 cmp_numerics(Numeric num1, Numeric num2)
 {
 	int			result;
+	BufferedNumericVar var1;
+	BufferedNumericVar var2;
+
+	num1 = init_buffered_var_from_num(num1, &var1);
+	num2 = init_buffered_var_from_num(num2, &var2);
 
 	/*
 	 * We consider all NANs to be equal and larger than any non-NAN (including
@@ -2714,7 +2894,7 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
 Datum
 hash_numeric(PG_FUNCTION_ARGS)
 {
-	Numeric		key = PG_GETARG_NUMERIC(0);
+	Numeric		key = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
 	Datum		digit_hash;
 	Datum		result;
 	int			weight;
@@ -2723,6 +2903,9 @@ hash_numeric(PG_FUNCTION_ARGS)
 	int			i;
 	int			hash_len;
 	NumericDigit *digits;
+	BufferedNumericVar var;
+
+	key = init_buffered_var_from_num(key, &var);
 
 	/* If it's NaN or infinity, don't try to hash the rest of the fields */
 	if (NUMERIC_IS_SPECIAL(key))
@@ -2865,12 +3048,16 @@ hash_numeric_extended(PG_FUNCTION_ARGS)
 Datum
 numeric_add(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_add_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2882,11 +3069,15 @@ numeric_add(PG_FUNCTION_ARGS)
 Numeric
 numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+
+	BufferedNumericVar result;
 	Numeric		res;
 
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2915,18 +3106,15 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_ninf);
 	}
 
-	/*
-	 * Unpack the values, let add_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
 
-	init_var(&result);
-	add_var(&arg1, &arg2, &result);
+	add_var(&arg1.var, &arg2.var, &result.var);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -2940,12 +3128,16 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_sub(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_sub_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2958,11 +3150,17 @@ numeric_sub(PG_FUNCTION_ARGS)
 Numeric
 numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let sub_var() compute the result and return it.
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2991,18 +3189,14 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_pinf);
 	}
 
-	/*
-	 * Unpack the values, let sub_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	sub_var(&arg1.var, &arg2.var, &result.var);
 
-	init_var(&result);
-	sub_var(&arg1, &arg2, &result);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3016,12 +3210,16 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_mul(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_mul_safe(num1, num2, fcinfo->context);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
 		PG_RETURN_NULL();
 
@@ -3037,11 +3235,25 @@ numeric_mul(PG_FUNCTION_ARGS)
 Numeric
 numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let mul_var() compute the result and return it.
+	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
+	 * case of numeric_mul(), which is invoked for the * operator on numerics,
+	 * we request exact representation for the product (rscale = sum(dscale of
+	 * arg1, dscale of arg2)).  If the exact result has more digits after the
+	 * decimal point than can be stored in a numeric, we round it.  Rounding
+	 * after computing the exact result ensures that the final result is
+	 * correctly rounded (rounding in mul_var() using a truncated product
+	 * would not guarantee this).
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3102,29 +3314,17 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 		Assert(false);
 	}
 
-	/*
-	 * Unpack the values, let mul_var() compute the result and return it.
-	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
-	 * case of numeric_mul(), which is invoked for the * operator on numerics,
-	 * we request exact representation for the product (rscale = sum(dscale of
-	 * arg1, dscale of arg2)).  If the exact result has more digits after the
-	 * decimal point than can be stored in a numeric, we round it.  Rounding
-	 * after computing the exact result ensures that the final result is
-	 * correctly rounded (rounding in mul_var() using a truncated product
-	 * would not guarantee this).
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	mul_var(&arg1.var, &arg2.var, &result.var, arg1.var.dscale + arg2.var.dscale);
 
-	init_var(&result);
-	mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
+	if (result.var.dscale > NUMERIC_DSCALE_MAX)
+		round_var(&result.var, NUMERIC_DSCALE_MAX);
 
-	if (result.dscale > NUMERIC_DSCALE_MAX)
-		round_var(&result, NUMERIC_DSCALE_MAX);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3138,12 +3338,16 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_div(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_div_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -3156,12 +3360,18 @@ numeric_div(PG_FUNCTION_ARGS)
 Numeric
 numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 	int			rscale;
 
+	/*
+	 * Unpack the arguments
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3209,31 +3419,27 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_zero);
 	}
 
-	/*
-	 * Unpack the arguments
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
-
-	init_var(&result);
+	init_buffered_var(&result);
 
 	/*
 	 * Select scale for division result
 	 */
-	rscale = select_div_scale(&arg1, &arg2);
+	rscale = select_div_scale(&arg1.var, &arg2.var);
 
 	/* Check for division by zero */
-	if (arg2.ndigits == 0 || arg2.digits[0] == 0)
+	if (arg2.var.ndigits == 0 || arg2.var.digits[0] == 0)
 		goto division_by_zero;
 
 	/*
 	 * Do the divide and return the result
 	 */
-	div_var(&arg1, &arg2, &result, rscale, true, true);
+	div_var(&arg1.var, &arg2.var, &result.var, rscale, true, true);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 
@@ -3450,17 +3656,17 @@ numeric_inc(PG_FUNCTION_ARGS)
 Datum
 numeric_smaller(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) < 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -3472,17 +3678,17 @@ numeric_smaller(PG_FUNCTION_ARGS)
 Datum
 numeric_larger(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) > 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -4854,7 +5060,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	state->N++;
 
 	/* Accumulate sums */
@@ -4862,7 +5067,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	if (state->calcSumX2)
 		accum_sum_add(&(state->sumX2), &X2);
-
 	MemoryContextSwitchTo(old_context);
 }
 
@@ -4932,7 +5136,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 			return false;
 		}
 	}
-
 	/* if we need X^2, calculate that in short-lived context */
 	if (state->calcSumX2)
 	{
@@ -4942,7 +5145,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	if (state->N-- > 1)
 	{
 		/* Negate X, to subtract it from the sum */
@@ -4960,14 +5162,11 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 	{
 		/* Zero the sums */
 		Assert(state->N == 0);
-
 		accum_sum_reset(&state->sumX);
 		if (state->calcSumX2)
 			accum_sum_reset(&state->sumX2);
 	}
-
 	MemoryContextSwitchTo(old_context);
-
 	return true;
 }
 
@@ -6689,6 +6888,80 @@ dump_var(const char *str, NumericVar *var)
 
 	printf("\n");
 }
+
+
+static inline int
+check_digit_overflow(const NumericVar *var)
+{
+	int			i;
+
+	for (i = 0; i < var->ndigits; i++)
+	{
+		if (var->digits[i] >= NBASE)
+		{
+			return 1;
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * check_numericvar_state() -
+ *
+ * Validate internal NumericVar storage-state invariants.
+ *
+ * This is primarily intended as a debugging aid for future changes to the
+ * NumericVar storage-state machinery.
+ */
+static inline void
+check_numericvar_state(const NumericVar *var, const char *where)
+{
+	Assert(var != NULL);
+	Assert(var->ndigits >= 0);
+
+	if (var->state != NUMVAR_BORROWED)
+		Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->buf == NULL);
+			Assert(var->digits == NULL);
+			Assert(var->capacity == 0);
+			break;
+
+		case NUMVAR_BORROWED:
+			Assert(var->buf == NULL);
+			Assert(var->capacity == 0);
+			Assert(var->borrow_kind != NUMVAR_BORROW_NONE);
+
+			if (!NUMERICVAR_IS_SPECIAL(var))
+				Assert(var->digits != NULL);
+			break;
+
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->inline_buf != NULL);
+			Assert(var->buf == var->inline_buf);
+			Assert(var->digits != NULL);
+			Assert(var->capacity == NUMERIC_INLINE_BUFFER_SIZE);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->buf != NULL);
+			Assert(var->digits != NULL);
+			Assert(var->capacity > 0);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		default:
+			elog(ERROR, "unexpected NumericVar state %d at %s",
+				 (int) var->state, where);
+	}
+}
 #endif							/* NUMERIC_DEBUG */
 
 
@@ -6703,35 +6976,296 @@ dump_var(const char *str, NumericVar *var)
  * ----------------------------------------------------------------------
  */
 
+/*
+ * digitbuf_get() -
+ *
+ * Ensure that a NumericVar has owned writable digit storage with at least the
+ * requested physical capacity, including any spare leading digit required by
+ * the caller.
+ *
+ * Regardless of the variable's current state, this function returns the base
+ * of writable owned storage and leaves the variable in either
+ * NUMVAR_OWNED_INLINE or NUMVAR_OWNED_HEAP state.
+ *
+ * This function is concerned only with storage acquisition and state
+ * transition.  Callers remain responsible for maintaining the logical numeric
+ * fields and for copying digit contents when required.
+ */
+static inline NumericDigit *
+digitbuf_get(NumericVar *var, int required_capacity)
+{
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf)
+			{
+				var->buf = var->inline_buf;
+				var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+				var->state = NUMVAR_OWNED_INLINE;
+			}
+			else
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_BORROWED:
+			if (var->borrow_kind == NUMVAR_BORROW_EXTERNAL)
+			{
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf != NULL)
+				{
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			else
+			{
+				Assert(var->inline_buf != NULL);
+				Assert(var->borrow_kind == NUMVAR_BORROW_INLINE);
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE)
+				{
+					/*
+					 * NUMVAR_BORROW_INLINE is currently used only for the
+					 * special case where the inline buffer of a
+					 * BufferedNumericVar holds a detoasted Numeric datum.
+					 *
+					 * In this case, the caller guarantees that the borrowed
+					 * buffered representation does not need to be preserved
+					 * as an independent read-only view. Therefore, when the
+					 * requested capacity fits in the inline home, we may
+					 * convert that storage to NUMVAR_OWNED_INLINE by
+					 * resetting buf to the beginning of the inline buffer and
+					 * reestablishing the normal NumericVar working-buffer
+					 * layout there.
+					 */
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			break;
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			Assert(var->inline_buf != NULL);
+			if (required_capacity > var->capacity)
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity > var->capacity)
+			{
+				pfree(var->buf);
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+			}
+			break;
+		default:
+			Assert(false);
+			return NULL;		/* silence compiler warning */
+	}
+
+	var->borrow_kind = NUMVAR_BORROW_NONE;
+	var->digits = var->buf + 1;
+
+	return var->buf;
+}
+
+
+/*
+ * digitbuf_free() -
+ *
+ * Release the current active digit storage or borrowed digit view of a
+ * NumericVar and return it to NUMVAR_EMPTY storage state.
+ *
+ * Heap-backed owned storage is pfree'd.  Inline owned storage is simply
+ * detached as the active buffer and remains available as the variable's
+ * inline home.  Borrowed storage is not freed, but any active borrowed view
+ * is dropped.
+ *
+ * This function resets only storage-state fields.  It does not by itself
+ * establish a meaningful numeric value.
+ */
+static inline void
+digitbuf_free(NumericVar *var)
+{
+	switch (var->state)
+	{
+		case NUMVAR_OWNED_HEAP:
+			pfree(var->buf);
+			pg_fallthrough;
+		case NUMVAR_EMPTY:
+		case NUMVAR_BORROWED:
+		case NUMVAR_OWNED_INLINE:
+			var->buf = NULL;
+			var->capacity = 0;
+			var->digits = NULL;
+			var->state = NUMVAR_EMPTY;
+			var->borrow_kind = NUMVAR_BORROW_NONE;
+			break;
+		default:
+			Assert(false);
+	}
+}
+
+
+/*
+ * result_aliases_input() -
+ *
+ * Return true if the destination variable cannot be used directly as the
+ * output of an arithmetic operation because it aliases one of the inputs.
+ *
+ * In addition to direct pointer equality, this treats result as aliasing if
+ * its current digit pointer falls within the active digit range of either
+ * input variable.
+ */
+static bool
+result_aliases_input(const NumericVar *result,
+					 const NumericVar *a,
+					 const NumericVar *b)
+{
+	if (result == a || result == b)
+		return true;
+
+	/*
+	 * Be conservative: if result currently points into either input digit
+	 * range, treat it as aliasing as well.
+	 */
+	if (result->digits != NULL)
+	{
+		if (a->digits != NULL &&
+			result->digits >= a->digits &&
+			result->digits < a->digits + a->ndigits)
+			return true;
+
+		if (b->digits != NULL &&
+			result->digits >= b->digits &&
+			result->digits < b->digits + b->ndigits)
+			return true;
+	}
+
+	return false;
+}
+
+
+/*
+ * prepare_result_var() -
+ *
+ * Prepare a writable result variable for an arithmetic operation.
+ *
+ * If the caller's destination does not alias either input, writable storage
+ * is allocated directly in that destination and the destination is returned.
+ *
+ * If the destination aliases an input, a temporary BufferedNumericVar is
+ * initialized and allocated instead, so that the operation may read its
+ * inputs without destroying their digit storage.
+ */
+static NumericVar *
+prepare_result_var(NumericVar *result,
+				   const NumericVar *a,
+				   const NumericVar *b,
+				   int res_ndigits,
+				   BufferedNumericVar * tmp)
+{
+	if (result_aliases_input(result, a, b))
+	{
+		init_buffered_var(tmp);
+
+		alloc_var(&tmp->var, res_ndigits);
+
+		return &tmp->var;
+	}
+
+	alloc_var(result, res_ndigits);
+
+	return result;
+}
+
+
+/*
+ * finish_result_var() -
+ *
+ * Finalize a result prepared by prepare_result_var().
+ *
+ * If the arithmetic result was computed directly in the caller's destination,
+ * this function does nothing.
+ *
+ * If the result was computed in a temporary variable, copy that value back
+ * into the caller's destination and release the temporary storage.
+ */
+static void
+finish_result_var(NumericVar *work_result, NumericVar *result)
+{
+	if (work_result != result)
+	{
+		set_var_from_var(work_result, result);
+		free_var(work_result);
+	}
+}
+
 
 /*
  * alloc_var() -
  *
- *	Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
+ * Allocate owned writable digit storage for a NumericVar large enough for the
+ * requested number of logical digits plus one spare leading digit.
+ *
+ * The variable is left in an owned writable state with basic working metadata
+ * initialized, but callers remain responsible for constructing the final
+ * numeric value.
  */
 static void
 alloc_var(NumericVar *var, int ndigits)
 {
-	digitbuf_free(var->buf);
-	var->buf = digitbuf_alloc(ndigits + 1);
-	var->buf[0] = 0;			/* spare digit for rounding */
-	var->digits = var->buf + 1;
+	digitbuf_free(var);
+
+	digitbuf_get(var, ndigits + 1);
+
 	var->ndigits = ndigits;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
+	var->buf[0] = 0;			/* ensure spare digit is zeroed */
 }
 
 
 /*
  * free_var() -
  *
- *	Return the digit buffer of a variable to the free pool
+ * Release the current active storage of a NumericVar and reset its numeric
+ * metadata to an empty working state.
+ *
+ * Unlike digitbuf_free(), this is a variable-level cleanup helper: it clears
+ * both storage-state and value-level fields.
  */
 static void
 free_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
-	var->buf = NULL;
-	var->digits = NULL;
-	var->sign = NUMERIC_NAN;
+	/* Release current active storage, if any. */
+	digitbuf_free(var);
+
+	var->ndigits = 0;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
 }
 
 
@@ -6744,7 +7278,7 @@ free_var(NumericVar *var)
 static void
 zero_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
+	free_var(var);
 	var->buf = NULL;
 	var->digits = NULL;
 	var->ndigits = 0;
@@ -6923,9 +7457,11 @@ set_var_from_str(const char *str, const char *cp,
 	ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
 	dest->sign = sign;
 	dest->weight = weight;
 	dest->dscale = dscale;
+	dest->digits = dest->buf + 1;
 
 	i = DEC_DIGITS - offset;
 	digits = dest->digits;
@@ -7184,6 +7720,8 @@ set_var_from_num(Numeric num, NumericVar *dest)
 	ndigits = NUMERIC_NDIGITS(num);
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
+	dest->digits = dest->buf + 1;
 
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
@@ -7196,50 +7734,187 @@ set_var_from_num(Numeric num, NumericVar *dest)
 /*
  * init_var_from_num() -
  *
- *	Initialize a variable from packed db format. The digits array is not
- *	copied, which saves some cycles when the resulting var is not modified.
- *	Also, there's no need to call free_var(), as long as you don't assign any
- *	other value to it (with set_var_* functions, or by using the var as the
- *	destination of a function like add_var())
+ * Initialize a NumericVar from an input Numeric datum, detoasting the datum
+ * first if necessary.
  *
- *	CAUTION: Do not modify the digits buffer of a var initialized with this
- *	function, e.g by calling round_var() or trunc_var(), as the changes will
- *	propagate to the original Numeric! It's OK to use it as the destination
- *	argument of one of the calculational functions, though.
+ * For finite values, the resulting NumericVar is a borrowed read-only view of
+ * externally owned digit storage, with borrow kind
+ * NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the detoasted representation actually used
+ * to initialize the borrowed view.
  */
-static void
+static Numeric
 init_var_from_num(Numeric num, NumericVar *dest)
 {
+	/* ensure the numeric is always detoasted */
+	num = (Numeric) PG_DETOAST_DATUM(NumericGetDatum(num));
+
+	init_var(dest);
+
 	dest->ndigits = NUMERIC_NDIGITS(num);
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
 	dest->dscale = NUMERIC_DSCALE(num);
-	dest->digits = NUMERIC_DIGITS(num);
-	dest->buf = NULL;			/* digits array is not palloc'd */
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->state = NUMVAR_EMPTY;
+		dest->borrow_kind = NUMVAR_BORROW_NONE;
+	}
+	else
+	{
+		dest->state = NUMVAR_BORROWED;
+		dest->borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+		dest->digits = NUMERIC_DIGITS(num);
+	}
+
+	return num;
+}
+
+
+/*
+ * init_buffered_var_from_num() -
+ *
+ * Initialize a BufferedNumericVar from an input Numeric datum, using the
+ * object's inline buffer as detoast workspace when possible.
+ *
+ * For finite values, the resulting NumericVar is a borrowed read-only view.
+ * If the detoasted representation is materialized in the inline buffer, the
+ * borrow kind is recorded as NUMVAR_BORROW_INLINE; otherwise it is recorded
+ * as NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the representation actually used to
+ * initialize the borrowed view.
+ */
+static Numeric
+init_buffered_var_from_num(Numeric num, BufferedNumericVar * dest)
+{
+	num = (Numeric) numeric_detoast_attr_to_buffer((varlena *) (num),
+												   (unsigned char *) dest->buffer,
+												   sizeof(dest->buffer));
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+		init_var(&dest->var);
+		dest->var.inline_buf = dest->buffer;
+		dest->var.sign = NUMERIC_SIGN(num);
+		return num;
+	}
+
+	dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&dest->var);
+	dest->var.inline_buf = dest->buffer;
+
+	dest->var.state = NUMVAR_BORROWED;
+	dest->var.ndigits = NUMERIC_NDIGITS(num);
+	dest->var.weight = NUMERIC_WEIGHT(num);
+	dest->var.sign = NUMERIC_SIGN(num);
+	dest->var.dscale = NUMERIC_DSCALE(num);
+	dest->var.digits = NUMERIC_DIGITS(num);
+
+	if ((unsigned char *) num == (unsigned char *) dest->buffer)
+		dest->var.borrow_kind = NUMVAR_BORROW_INLINE;
+	else
+		dest->var.borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+	return num;
+}
+
+
+/*
+ * numeric_detoast_attr_to_buffer() -
+ *
+ * Convert a varlena datum to a detoasted representation, preferably using a
+ * caller-provided buffer when that is sufficient.
+ *
+ * This helper is intended for numeric paths.  If the input datum is in
+ * short-header form, it is expanded to ordinary 4-byte-header form, using
+ * the supplied buffer when possible and falling back to palloc'd storage
+ * otherwise.
+ *
+ * If the input datum is not in short-header form, this helper falls back to
+ * detoast_attr().
+ *
+ * The returned pointer may therefore refer either to the caller-provided
+ * buffer or to freshly allocated storage.
+ */
+static varlena *
+numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+							   size_t buffer_size)
+{
+	Size		data_size;
+	Size		new_size;
+	varlena    *new_attr;
+
+	if (!VARATT_IS_SHORT(attr))
+		return pg_detoast_datum(attr);
+
+	/*
+	 * This is a short-header varlena --- convert to 4-byte header format
+	 */
+	data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
+	new_size = data_size + VARHDRSZ;
+
+	if (unlikely(buffer_size < new_size))
+		new_attr = (varlena *) palloc(new_size);
+	else
+		new_attr = (varlena *) buffer;
+	SET_VARSIZE(new_attr, new_size);
+	memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
+	attr = new_attr;
+
+	return attr;
 }
 
 
 /*
  * set_var_from_var() -
  *
- *	Copy one variable into another
+ * Copy one NumericVar into another.
+ *
+ * The destination receives the same numeric value as the source, but this
+ * function always constructs a destination representation that is valid for
+ * the destination's own storage state.  It does not steal storage from the
+ * source.
+ *
+ * This function must remain safe when source and destination are the same
+ * variable.
  */
 static void
 set_var_from_var(const NumericVar *value, NumericVar *dest)
 {
-	NumericDigit *newbuf;
+	if (value == dest)
+		return;
 
-	newbuf = digitbuf_alloc(value->ndigits + 1);
-	newbuf[0] = 0;				/* spare digit for rounding */
-	if (value->ndigits > 0)		/* else value->digits might be null */
-		memcpy(newbuf + 1, value->digits,
-			   value->ndigits * sizeof(NumericDigit));
+	if (NUMERICVAR_IS_SPECIAL(value))
+	{
+		free_var(dest);
+		dest->sign = value->sign;
+
+		return;
+	}
 
-	digitbuf_free(dest->buf);
+	/* ndigits is set in alloc_var */
+	alloc_var(dest, value->ndigits);
 
-	memmove(dest, value, sizeof(NumericVar));
-	dest->buf = newbuf;
-	dest->digits = newbuf + 1;
+	dest->weight = value->weight;
+	dest->dscale = value->dscale;
+	dest->sign = value->sign;
+
+	memcpy(dest->digits,
+		   value->digits,
+		   value->ndigits * sizeof(NumericDigit));
 }
 
 
@@ -8600,6 +9275,7 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
 	 */
 	result->weight = res_weight;
 	result->sign = res_sign;
+	result->ndigits = res_ndigits;
 
 	/* Round to target rscale (and set result->dscale) */
 	round_var(result, rscale);
@@ -8631,6 +9307,8 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	NumericDigit *res_digits;
 	uint32		carry = 0;
 	uint32		term;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Check preconditions */
 	Assert(var1ndigits >= 1);
@@ -8651,8 +9329,9 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	res_weight = var1->weight + var2->weight + 1;
 	res_ndigits = var1ndigits + var2ndigits;
 
-	/* Allocate result digit array */
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -8872,16 +9551,17 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	}
 
 	/* Store the product in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
-	result->dscale = var1->dscale + var2->dscale;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = var1->dscale + var2->dscale;
 
 	/* Strip leading and trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -9457,6 +10137,8 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint32		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9490,7 +10172,9 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9532,12 +10216,14 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -9573,6 +10259,8 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint64		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9606,7 +10294,9 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9648,12 +10338,13 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -11494,6 +12185,8 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			carry = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11514,7 +12207,9 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11543,15 +12238,15 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(carry == 0);			/* else we failed to allow for carry out */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -11579,6 +12274,8 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			borrow = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11599,7 +12296,9 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11628,15 +12327,15 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(borrow == 0);		/* else caller gave us var1 < var2 */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 /*
@@ -12099,7 +12798,9 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 
 	/* Create NumericVars representing the positive and negative sums */
 	init_var(&pos_var);
+	alloc_var(&pos_var, accum->ndigits);
 	init_var(&neg_var);
+	alloc_var(&neg_var, accum->ndigits);
 
 	pos_var.ndigits = neg_var.ndigits = accum->ndigits;
 	pos_var.weight = neg_var.weight = accum->weight;
@@ -12107,8 +12808,8 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 	pos_var.sign = NUMERIC_POS;
 	neg_var.sign = NUMERIC_NEG;
 
-	pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits);
-	neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits);
+	pos_var.buf = pos_var.digits = digitbuf_alloc_raw(accum->ndigits);
+	neg_var.buf = neg_var.digits = digitbuf_alloc_raw(accum->ndigits);
 
 	for (i = 0; i < accum->ndigits; i++)
 	{
-- 
2.43.0



  [application/octet-stream] 0002-numeric-add-regression-coverage-for-NumericVar-state.patch (131.9K, ../../[email protected]/4-0002-numeric-add-regression-coverage-for-NumericVar-state.patch)
  download | inline diff:
From 840c56c98511f50e997371cab16ec187c042f857 Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:43:50 +0800
Subject: [PATCH 2/2] numeric: add regression coverage for NumericVar state
 changes

Add regression coverage for the NumericVar storage-state refactoring.

The tests cover a wide range of numeric typmods, including high-precision
and high-scale cases, and exercise arithmetic, composite expressions,
comparison-sensitive paths, window functions, join-driven expressions,
aggregates, updates, error cases, and index/index-only related paths.

This is intended to verify that the refactoring preserves SQL-visible
behavior across both ordinary and extreme numeric workloads.
---
 src/test/regress/expected/numeric_op_big.out  | 487 ++++++++++++++
 src/test/regress/expected/numeric_op_ddl.out  | 167 +++++
 .../regress/expected/numeric_op_medium.out    | 487 ++++++++++++++
 .../regress/expected/numeric_op_small.out     | 636 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   4 +
 src/test/regress/sql/numeric_op_big.sql       | 269 ++++++++
 src/test/regress/sql/numeric_op_ddl.sql       | 173 +++++
 src/test/regress/sql/numeric_op_medium.sql    | 269 ++++++++
 src/test/regress/sql/numeric_op_small.sql     | 335 +++++++++
 9 files changed, 2827 insertions(+)
 create mode 100644 src/test/regress/expected/numeric_op_big.out
 create mode 100644 src/test/regress/expected/numeric_op_ddl.out
 create mode 100644 src/test/regress/expected/numeric_op_medium.out
 create mode 100644 src/test/regress/expected/numeric_op_small.out
 create mode 100644 src/test/regress/sql/numeric_op_big.sql
 create mode 100644 src/test/regress/sql/numeric_op_ddl.sql
 create mode 100644 src/test/regress/sql/numeric_op_medium.sql
 create mode 100644 src/test/regress/sql/numeric_op_small.sql

diff --git a/src/test/regress/expected/numeric_op_big.out b/src/test/regress/expected/numeric_op_big.out
new file mode 100644
index 00000000000..be0c51a9b2e
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_big.out
@@ -0,0 +1,487 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -93674011458
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 49431437020
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | 81596504540
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum      |     sum     |     sum     |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+-------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 103787738890 | 71241136711 | 91238480326 | -25880446966 | 85861504452 | 138111163668 | -64934216831 | -3883271108300 | -3883271108300 | -7965490469700 | -263811485216 | -196222417055 | -253470347100
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+      sum      |     sum      |     sum     |     sum      |      sum      |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+---------------+--------------+-------------+--------------+---------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ -168835944407 | -37026039983 | -9162875272 | 103006726746 | -108905948664 | -77309522276 | 146591765000 | -3883271108300 | -3883271108300 | -7965490469700 | -111504876460 | -183625584196 | -243750883157
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |     sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+--------------+--------------
+ 81596504540 | 81596504540 | -448199909000 |     |     | -448199909000 | 81596504540 | 153330623000 | -1995205883000 | -1995205883000 | 81596504540 | -70370694025 | -81922915700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -153072055232
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -221944827430
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ -87303812940 | -87303812940 | -36200000898
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Index Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 187915496731
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |      sum      
+-------+---------------
+  2001 | -120277609243
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | -6791326615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum     |     sum      |     sum     |    sum     |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum      |     sum      |     sum      
+-------------+--------------+-------------+------------+-------------+-------------+-------------+----------------+----------------+----------------+--------------+--------------+--------------
+ 69378299805 | -28004625396 | 69165702075 | 6495822165 | 42794676167 | 28778131261 | 23748489343 | -3668417457700 | -3883271108300 | -8180344120300 | 162788189344 | -88035415965 | -14300392972
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+     sum      |    sum    |     sum      |     sum      |     sum      |    sum     |     sum      |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+-----------+--------------+--------------+--------------+------------+--------------+----------------+----------------+----------------+---------------+--------------+---------------
+ 106142378501 | 456865251 | -40344563826 | -82057607022 | -74296237953 | 4474319379 | 111919427572 | -3668417457700 | -3883271108300 | -8180344120300 | -223137274026 | -60159164731 | -114299102966
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |      sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+---------------+--------------
+ -6791326615 | -6791326615 | -448199909000 |     |     | -448199909000 | -6791326615 | 153330623000 | -1995205883000 | -1995205883000 | -6791326615 | -116888674645 | -70370694025
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -23880525307
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 31543863976
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ -87303812940 | -87303812940 | 18056325685
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+     sum      
+--------------
+ -27571036926
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+     sum      
+--------------
+ 142042826843
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
+     sum      
+--------------
+ -37293091674
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_ddl.out b/src/test/regress/expected/numeric_op_ddl.out
new file mode 100644
index 00000000000..d8a1523a5fc
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_ddl.out
@@ -0,0 +1,167 @@
+DROP TABLE IF EXISTS t_9_2;
+NOTICE:  table "t_9_2" does not exist, skipping
+DROP TABLE IF EXISTS t_18_2;
+NOTICE:  table "t_18_2" does not exist, skipping
+DROP TABLE IF EXISTS t_38_2;
+NOTICE:  table "t_38_2" does not exist, skipping
+DROP TABLE IF EXISTS t_80_16;
+NOTICE:  table "t_80_16" does not exist, skipping
+DROP TABLE IF EXISTS t_175_35;
+NOTICE:  table "t_175_35" does not exist, skipping
+DROP TABLE IF EXISTS t_400_80;
+NOTICE:  table "t_400_80" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_200;
+NOTICE:  table "t_1000_200" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_800;
+NOTICE:  table "t_1000_800" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_995;
+NOTICE:  table "t_1000_995" does not exist, skipping
+DROP TABLE IF EXISTS t_base;
+NOTICE:  table "t_base" does not exist, skipping
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+DROP TABLE IF EXISTS t_1000_1000;
+NOTICE:  table "t_1000_1000" does not exist, skipping
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+VACUUM FREEZE ANALYZE t_1000_1000;
diff --git a/src/test/regress/expected/numeric_op_medium.out b/src/test/regress/expected/numeric_op_medium.out
new file mode 100644
index 00000000000..bf4655f59bd
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_medium.out
@@ -0,0 +1,487 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -41380769803
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -17147548455
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -87880682140
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum      |     sum      |     sum     |     sum     |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+--------------+-------------+-------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 328096480219 | 106894971180 | 29998131312 | 16824177090 | -98601154544 | -84446300842 | -142367027518 | -3883271108300 | -3883271108300 | -7965490469700 | -31882620402 | -214909270261 | -312201369170
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |      sum      |     sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+---------------+--------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+---------------+--------------+---------------
+ -89713139799 | -218213819552 | 100513273638 | -79355358716 | 45044419840 | -3009527768 | 27142855016 | -3883271108300 | -3883271108300 | -7965490469700 | -248734189206 | -85072914373 | -210488126609
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+    sum    |     sum      |     sum      |     sum      |     sum      |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+-----------+--------------+--------------+--------------+--------------+-------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 534102535 | 222800148124 | 252518296200 | -40723661604 | -53446320231 | 49858073732 | 23224917403 | -3883271108300 | -3883271108300 | -7965490469700 | -212735400480 | -257086925922 | -206858526458
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 73062391064
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | 5986675256
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -10341341254
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+                QUERY PLAN                 
+-------------------------------------------
+ Index Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+                QUERY PLAN                 
+-------------------------------------------
+ Index Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -89558437612
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -19722210906
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 277783880994
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum     |     sum      |     sum      |     sum     |      sum      |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |     sum      
+-------------+--------------+--------------+-------------+---------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+--------------
+ 48377864776 | -49822309848 | 178050220784 | 16824177090 | -138513601794 | 194342415281 | 28892192658 | -3668417457700 | -3883271108300 | -8180344120300 | -151559852522 | -152028559294 | -63268316355
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |     sum      |     sum      |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |     sum      |     sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+--------------+--------------+---------------
+ -67452832312 | -89225471931 | 146580491810 | -79355358716 | -9287051683 | 147260222842 | -70335569319 | -3668417457700 | -3883271108300 | -8180344120300 | -32938146279 | -59204444070 | -108234520264
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+      sum      |      sum      |    sum    |     sum      |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+-----------+--------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -163275209177 | -193968650050 | 679620265 | -40723661604 | -40601882101 | 147411738034 | -193122653902 | -3668417457700 | -3883271108300 | -8180344120300 | 270045436866 | -108867098352 | -172351504959
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -35696475290
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -203092149118
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -75070192124
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+     sum      
+--------------
+ -27666707523
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+     sum     
+-------------
+ 68424688419
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
+     sum      
+--------------
+ -58373747124
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_small.out b/src/test/regress/expected/numeric_op_small.out
new file mode 100644
index 00000000000..45b511b8ff5
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_small.out
@@ -0,0 +1,636 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 25264177945
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -18318735365
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 77086657878
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 51102838669
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+     sum      |     sum     |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 185332843643 | 74295396054 | -265653763404 | -130951372896 | 33926686039 | -93220527164 | 53982021999 | -3883271108300 | -3883271108300 | -7965490469700 | -136748774422 | -156349489824 | -229516261618
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+     sum      |    sum     |     sum      |      sum      |      sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+------------+--------------+---------------+---------------+---------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 205522402955 | 1737491244 | -98734378124 | -156012333362 | -122318628351 | -265348319222 | 53131151046 | -3883271108300 | -3883271108300 | -7965490469700 | -74716919418 | -132260756970 | -267366558171
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+     sum      |     sum      |      sum      |      sum      |     sum      |     sum      |     sum      |      sum       |      sum       |      sum       |    sum     |      sum      |      sum      
+--------------+--------------+---------------+---------------+--------------+--------------+--------------+----------------+----------------+----------------+------------+---------------+---------------
+ 188179625901 | 112456755410 | -343776078076 | -174825681708 | -71003178193 | 142439459768 | 114154703061 | -3883271108300 | -3883271108300 | -7965490469700 | 8740136096 | -345863766775 | -368207427216
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum     |      sum      |     sum      |      sum      |      sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+-------------+---------------+--------------+---------------+---------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 159659505470 | -1983944819 | -319417093044 | -11104453418 | -158100462876 | -145951162894 | -124997818319 | -3883271108300 | -3883271108300 | -7965490469700 | 146077391644 | -208253897723 | -211323905998
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 47811429266
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -17989105700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -79591213532
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -190878347020
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+             QUERY PLAN              
+-------------------------------------
+ Index Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+               QUERY PLAN                
+-----------------------------------------
+ Index Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 55192112654
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 79276791029
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 119235302735
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 109095039015
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+      sum      |      sum      |      sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum     |     sum      |     sum      
+---------------+---------------+---------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+-------------+--------------+--------------
+ -198398285973 | -268636739265 | -185796812937 | -80212568293 | 40366812062 | 50022231011 | 86359907719 | -3668417457700 | -3883271108300 | -8180344120300 | 88627277998 | -83578376254 | -56047555653
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+      sum      |      sum      |      sum      |     sum      |     sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+---------------+---------------+---------------+--------------+--------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ -198076509004 | -185931021498 | -174261325669 | -62721548562 | -87570938488 | -151149981174 | 46905358609 | -3668417457700 | -3883271108300 | -8180344120300 | 66791978874 | -162795673535 | -140859793615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+      sum      |      sum      |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -173598401621 | -191141226891 | -153991008984 | -174825681708 | -9994590731 | 103039770436 | 90917337737 | -3668417457700 | -3883271108300 | -8180344120300 | 212488760840 | -157402192456 | -222636044392
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum      |     sum      |     sum      |     sum     |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ 179235436643 | 248686171421 | -90086495035 | -11104453418 | 11551494962 | -229139601272 | 32577271698 | -3668417457700 | -3883271108300 | -8180344120300 | 81696122255 | -125106925825 | -150252904102
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -13202507172
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | -696258940
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 19679287493
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | -6889081579
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+     sum      
+--------------
+ -16440552090
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+     sum      
+--------------
+ 104998257177
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+    sum     
+------------
+ 9189839531
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
+      sum      
+---------------
+ -126456520242
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..926010b47cb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -146,3 +146,7 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# this test is to check if numeric operations are correct
+test: numeric_op_ddl
+test: numeric_op_small numeric_op_medium numeric_op_big
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_big.sql b/src/test/regress/sql/numeric_op_big.sql
new file mode 100644
index 00000000000..b8cff14fa93
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_big.sql
@@ -0,0 +1,269 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_ddl.sql b/src/test/regress/sql/numeric_op_ddl.sql
new file mode 100644
index 00000000000..5d213c75acd
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_ddl.sql
@@ -0,0 +1,173 @@
+DROP TABLE IF EXISTS t_9_2;
+DROP TABLE IF EXISTS t_18_2;
+DROP TABLE IF EXISTS t_38_2;
+DROP TABLE IF EXISTS t_80_16;
+DROP TABLE IF EXISTS t_175_35;
+DROP TABLE IF EXISTS t_400_80;
+DROP TABLE IF EXISTS t_1000_200;
+DROP TABLE IF EXISTS t_1000_800;
+DROP TABLE IF EXISTS t_1000_995;
+
+DROP TABLE IF EXISTS t_base;
+
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+
+DROP TABLE IF EXISTS t_1000_1000;
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_1000_1000;
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_medium.sql b/src/test/regress/sql/numeric_op_medium.sql
new file mode 100644
index 00000000000..079dd414d19
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_medium.sql
@@ -0,0 +1,269 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_small.sql b/src/test/regress/sql/numeric_op_small.sql
new file mode 100644
index 00000000000..90d50d24d9c
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_small.sql
@@ -0,0 +1,335 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: [PATCH v4] Make NumericVar storage semantics explicit
@ 2026-05-16 14:32  =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  parent: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: =?utf-8?B?Q2hlbmh1aU1v?= @ 2026-05-16 14:32 UTC (permalink / raw)
  To: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>; pgsql-hackers

Hi all,

Attached is v4 of this patch.

The previous refresh accidentally omitted the fix for the UBSan issue in
set_var_from_var().  This version restores that fix and refreshes the
attached patch set.

No other design changes are intended in this update.

Thanks.
Regards,
Chenhui Mo

Attachments:

  [application/octet-stream] 0001-numeric-make-NumericVar-storage-states-explicit-and-.patch (56.2K, ../../[email protected]/3-0001-numeric-make-NumericVar-storage-states-explicit-and-.patch)
  download | inline diff:
From 6a79ce60efd8283666581e756a718d1f452abb79 Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:42:17 +0800
Subject: [PATCH 1/2] numeric: make NumericVar storage states explicit and use
 them in core paths

Clarify the in-memory storage model of NumericVar by making buffer
ownership and mutability states explicit, and reorganize the related
helper layer around that model.

This introduces explicit storage states for empty, borrowed,
owned-inline, and owned-heap representations, together with
borrow-kind tracking for borrowed digit views. It also adds
BufferedNumericVar as the inline-home wrapper used by the new model.

The patch then connects the new helper semantics to core comparison,
hashing, and basic arithmetic paths, while keeping higher-level numeric
algorithms structurally close to the existing implementation.

The goal is not to alter SQL-visible numeric behavior, but to make the
existing internal contract around NumericVar storage easier to
understand, document, and maintain.
---
 src/backend/utils/adt/numeric.c | 1198 ++++++++++++++++++++++++-------
 1 file changed, 950 insertions(+), 248 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..b92935c624f 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -104,6 +104,7 @@ typedef int16 NumericDigit;
 #endif
 
 #define NBASE_SQR	(NBASE * NBASE)
+#define NUMERIC_INLINE_BUFFER_SIZE 128
 
 /*
  * The Numeric type as stored on disk.
@@ -261,66 +262,165 @@ struct NumericData
 #define NUMERIC_WEIGHT_MAX			PG_INT16_MAX
 
 /* ----------
- * NumericVar is the format we use for arithmetic.  The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array.  If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight.  Another way to say it is that there are weight+1
- * digits before the decimal point.  It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar.  digits points at the first digit in actual use (the one
- * with the specified weight).  We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space.  We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present.  NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
+ * NumericVar is the in-memory working representation of a numeric value.
+ *
+ * A NumericVar always stores the logical numeric metadata (ndigits, weight,
+ * sign, dscale), but the physical storage backing digits[] is stateful.
+ *
+ * There are four storage states:
+ *
+ * 1. NUMVAR_EMPTY
+ *    The variable carries no digits storage and no digit payload.
+ *
+ * 2. NUMVAR_BORROWED
+ *    digits points to storage owned by some other representation.
+ *    In this state the current NumericVar does not own writable digit
+ *    storage, and buf is NULL.
+ *
+ *    Borrowed storage is further classified by borrow_kind:
+ *
+ *    - NUMVAR_BORROW_EXTERNAL
+ *      digits points into external numeric storage, such as a detoasted
+ *      Numeric datum.  This storage is read-only from the NumericVar's
+ *      perspective and cannot be upgraded in-place to writable inline
+ *      storage.
+ *
+ *    - NUMVAR_BORROW_INLINE
+ *      digits points into numeric storage currently materialized inside the
+ *      inline buffer of an associated BufferedNumericVar.  This is still a
+ *      read-only borrowed view, but because the underlying memory belongs to
+ *      the same buffered home, it may later be converted into writable inline
+ *      working storage.
+ *
+ * 3. NUMVAR_OWNED_INLINE
+ *    buf points to the inline buffer associated with this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * 4. NUMVAR_OWNED_HEAP
+ *    buf points to palloc'd writable storage owned by this NumericVar, and
+ *    digits points somewhere within that owned writable storage.
+ *
+ * In owned states, buf is the base of the current writable digit storage and
+ * digits points to the logical first digit.  As in the historical NumericVar
+ * representation, owned storage is initially allocated with a spare leading
+ * digit so that carry propagation can enlarge the value without immediate
+ * reallocation.  Stored numeric datums do not have this extra working space;
+ * it exists only in the in-memory working representation.  Therefore, a
+ * borrowed view of a stored/detoasted Numeric datum must not be treated as a
+ * writable work buffer without an explicit state transition.
+ *
+ * Note, however, that digits is not required to remain equal to buf + 1 for
+ * the lifetime of an owned NumericVar.  Subsequent normalization steps such as
+ * stripping leading zeroes may advance digits within the owned buffer, as long
+ * as the active digit range remains within the storage owned by buf.
+ *
+ * inline_buf, when non-NULL, identifies the fixed-size inline home provided by
+ * BufferedNumericVar.  It does not by itself imply that the variable is
+ * currently in NUMVAR_OWNED_INLINE state.  For example, a NumericVar may be in
+ * NUMVAR_BORROWED or NUMVAR_OWNED_HEAP state while still retaining an inline
+ * home for possible future reuse.
+ *
+ * capacity is the physical capacity of the current owned buffer, including the
+ * spare leading digit.  It is meaningful only in owned states, and is zero in
+ * empty or borrowed states.
+ *
+ * As with the traditional implementation, variable-level functions should be
+ * written so that a NumericVar may safely be used as both input and
+ * destination.  State transitions must preserve that property.
+ *
+ * NumericVar state invariants:
+ *
+ * NUMVAR_EMPTY:
+ *     buf == NULL
+ *     digits == NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *
+ * NUMVAR_BORROWED:
+ *     buf == NULL
+ *     borrow_kind != NUMVAR_BORROW_NONE
+ *     capacity == 0
+ *     digits points to borrowed storage, unless sign is special
+ *
+ * NUMVAR_OWNED_INLINE:
+ *     buf == inline_buf
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity == NUMERIC_INLINE_BUFFER_SIZE
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * NUMVAR_OWNED_HEAP:
+ *     buf != NULL
+ *     digits != NULL
+ *     borrow_kind == NUMVAR_BORROW_NONE
+ *     capacity > 0
+ *     the active digit range [digits, digits + ndigits) lies within
+ *     the owned storage range [buf, buf + capacity)
+ *
+ * Special values are represented through sign, not through a separate storage
+ * state.  For NaN and infinities, only sign is semantically meaningful.
  * ----------
  */
+
+typedef enum NUMERIC_VAR_STATUS
+{
+	NUMVAR_EMPTY,
+	NUMVAR_BORROWED,
+	NUMVAR_OWNED_INLINE,
+	NUMVAR_OWNED_HEAP
+}			NUMERIC_VAR_STATUS;
+
+typedef enum NUMERIC_BORROW_KIND
+{
+	NUMVAR_BORROW_NONE,
+	NUMVAR_BORROW_EXTERNAL,
+	NUMVAR_BORROW_INLINE
+}			NUMERIC_BORROW_KIND;
+
 typedef struct NumericVar
 {
-	int			ndigits;		/* # of digits in digits[] - can be 0! */
+	/* logical numeric metadata */
+	int			ndigits;		/* # of digits in digits[]; may be 0 */
 	int			weight;			/* weight of first digit */
 	int			sign;			/* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
 	int			dscale;			/* display scale */
-	NumericDigit *buf;			/* start of palloc'd space for digits[] */
-	NumericDigit *digits;		/* base-NBASE digits */
+
+	/* storage state metadata */
+	NUMERIC_VAR_STATUS state;	/* current storage state */
+	NUMERIC_BORROW_KIND borrow_kind;	/* source of borrowed digits, if
+										 * borrowed */
+	int			capacity;		/* current owned buffer capacity, spare digit
+								 * included */
+
+	/* current digit storage */
+	NumericDigit *buf;			/* base of current owned writable storage, or
+								 * NULL */
+	NumericDigit *digits;		/* logical first base-NBASE digit, or NULL */
+
+	/* optional fixed-size inline home */
+	NumericDigit *inline_buf;	/* inline buffer of associated
+								 * BufferedNumericVar, or NULL */
 } NumericVar;
 
+#define NUMERIC_VARLENA_SIZE (VARHDRSZ / sizeof(NumericDigit) + \
+	(sizeof(uint16) + sizeof(int16)) / sizeof(NumericDigit) + \
+	NUMERIC_INLINE_BUFFER_SIZE + DIV_GUARD_DIGITS + 4)
+
+typedef struct BufferedNumericVar
+{
+	NumericVar	var;
+	int64		magic;			/* used for align with 8-byte */
+	NumericDigit buffer[NUMERIC_VARLENA_SIZE];
+}			BufferedNumericVar;
+
+
+#define BUFFERED_NUMERIC_VAR_MAGIC 0x4E554D455249435F	/* 'NUMERIC_MAGIC' */
+
+#define NUMERICVAR_IS_SPECIAL(v) \
+    ((v)->sign == NUMERIC_NAN || (v)->sign == NUMERIC_PINF || (v)->sign == NUMERIC_NINF)
+
 
 /* ----------
  * Data for generate_series
@@ -415,18 +515,18 @@ typedef struct NumericSumAccum
  */
 static const NumericDigit const_zero_data[1] = {0};
 static const NumericVar const_zero =
-{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data};
+{0, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_data, NULL};
 
 static const NumericDigit const_one_data[1] = {1};
 static const NumericVar const_one =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericVar const_minus_one =
-{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
+{1, 0, NUMERIC_NEG, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_data, NULL};
 
 static const NumericDigit const_two_data[1] = {2};
 static const NumericVar const_two =
-{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
+{1, 0, NUMERIC_POS, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_two_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_zero_point_nine_data[1] = {9000};
@@ -436,7 +536,7 @@ static const NumericDigit const_zero_point_nine_data[1] = {90};
 static const NumericDigit const_zero_point_nine_data[1] = {9};
 #endif
 static const NumericVar const_zero_point_nine =
-{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data};
+{1, -1, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_zero_point_nine_data, NULL};
 
 #if DEC_DIGITS == 4
 static const NumericDigit const_one_point_one_data[2] = {1, 1000};
@@ -446,28 +546,31 @@ static const NumericDigit const_one_point_one_data[2] = {1, 10};
 static const NumericDigit const_one_point_one_data[2] = {1, 1};
 #endif
 static const NumericVar const_one_point_one =
-{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data};
+{2, 0, NUMERIC_POS, 1, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, (NumericDigit *) const_one_point_one_data, NULL};
 
 static const NumericVar const_nan =
-{0, 0, NUMERIC_NAN, 0, NULL, NULL};
+{0, 0, NUMERIC_NAN, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_pinf =
-{0, 0, NUMERIC_PINF, 0, NULL, NULL};
+{0, 0, NUMERIC_PINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 static const NumericVar const_ninf =
-{0, 0, NUMERIC_NINF, 0, NULL, NULL};
+{0, 0, NUMERIC_NINF, 0, NUMVAR_BORROWED, NUMVAR_BORROW_EXTERNAL, 0, NULL, NULL, NULL};
 
 #if DEC_DIGITS == 4
 static const int round_powers[4] = {0, 1000, 100, 10};
 #endif
 
-
 /* ----------
  * Local functions
  * ----------
  */
 
 #ifdef NUMERIC_DEBUG
+static inline int check_digit_overflow(const NumericVar *var);
+static inline void check_numericvar_state(const NumericVar *var, const char *where);
+#define CHECK_NUMERICVAR(v) check_numericvar_state((v), __func__)
+#define CHECK_NUMERICVAR_AT(v, where) check_numericvar_state((v), (where))
 static void dump_numeric(const char *str, Numeric num);
 static void dump_var(const char *str, NumericVar *var);
 #else
@@ -475,15 +578,65 @@ static void dump_var(const char *str, NumericVar *var);
 #define dump_var(s,v)
 #endif
 
-#define digitbuf_alloc(ndigits)  \
-	((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit)))
-#define digitbuf_free(buf)	\
-	do { \
-		 if ((buf) != NULL) \
-			 pfree(buf); \
-	} while (0)
+/*
+ * digitbuf_alloc_raw() -
+ *
+ * Allocate raw heap-backed digit storage with the specified physical
+ * capacity.
+ *
+ * The capacity is measured in NumericDigit units and includes any spare
+ * leading digit space required by the caller.
+ *
+ * This is a low-level storage primitive.  It does not inspect or update any
+ * NumericVar state, and it does not by itself establish a complete working
+ * NumericVar representation.
+ */
+static inline NumericDigit *
+digitbuf_alloc_raw(int sz)
+{
+	return (NumericDigit *) palloc(sizeof(NumericDigit) * sz);
+}
 
-#define init_var(v)		memset(v, 0, sizeof(NumericVar))
+/*
+ * init_var() -
+ *
+ * Initialize a NumericVar in NUMVAR_EMPTY storage state, with no active digit
+ * storage, no borrowed digit view, and no current digit payload.
+ *
+ * This establishes a clean starting point for later transitions into
+ * borrowed or owned states.
+ */
+static inline void
+init_var(NumericVar *v)
+{
+	memset(v, 0, sizeof(NumericVar));
+}
+
+/*
+ * init_buffered_var() -
+ *
+ * Initialize a BufferedNumericVar so that its embedded NumericVar starts with
+ * writable inline working storage backed by the object's fixed-size inline
+ * buffer.
+ *
+ * The inline buffer is installed as the current owned storage, and the digit
+ * pointer is initialized to the normal working position after the spare
+ * leading digit.
+ */
+static void
+init_buffered_var(BufferedNumericVar * bvar)
+{
+	bvar->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&bvar->var);
+
+	bvar->var.inline_buf = bvar->buffer;
+	bvar->var.capacity = NUMERIC_INLINE_BUFFER_SIZE;
+	bvar->var.state = NUMVAR_OWNED_INLINE;
+	bvar->var.borrow_kind = NUMVAR_BORROW_NONE;
+	bvar->var.buf = bvar->buffer;
+	bvar->var.buf[0] = 0;
+	bvar->var.digits = bvar->buffer + 1;
+}
 
 #define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
 	(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
@@ -494,8 +647,27 @@ static void dump_var(const char *str, NumericVar *var);
 	(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
 	(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
 
+static void digitbuf_free(NumericVar *var);
+
+static NumericDigit *digitbuf_get(NumericVar *var,
+								  int require_ndigits);
+
 static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
+
+static bool result_aliases_input(const NumericVar *result,
+								 const NumericVar *a,
+								 const NumericVar *b);
+
+static NumericVar *prepare_result_var(NumericVar *result,
+									  const NumericVar *a,
+									  const NumericVar *b,
+									  int res_ndigits,
+									  BufferedNumericVar * tmp);
+
+static void finish_result_var(NumericVar *work_result,
+							  NumericVar *result);
+
 static void zero_var(NumericVar *var);
 
 static bool set_var_from_str(const char *str, const char *cp,
@@ -507,7 +679,10 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
 												 const char **endptr,
 												 Node *escontext);
 static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_var_from_num(Numeric num, NumericVar *dest);
+static Numeric init_buffered_var_from_num(Numeric src, BufferedNumericVar * bvar);
+static varlena *numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+											   size_t buffer_size);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
 static char *get_str_from_var(const NumericVar *var);
 static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -543,6 +718,7 @@ static int	cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
 						   int var1weight, int var1sign,
 						   const NumericDigit *var2digits, int var2ndigits,
 						   int var2weight, int var2sign);
+
 static void add_var(const NumericVar *var1, const NumericVar *var2,
 					NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -2416,8 +2592,8 @@ numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
 Datum
 numeric_cmp(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	int			result;
 
 	result = cmp_numerics(num1, num2);
@@ -2428,12 +2604,11 @@ numeric_cmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
-
 Datum
 numeric_eq(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) == 0;
@@ -2447,8 +2622,8 @@ numeric_eq(PG_FUNCTION_ARGS)
 Datum
 numeric_ne(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) != 0;
@@ -2462,8 +2637,8 @@ numeric_ne(PG_FUNCTION_ARGS)
 Datum
 numeric_gt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) > 0;
@@ -2477,8 +2652,8 @@ numeric_gt(PG_FUNCTION_ARGS)
 Datum
 numeric_ge(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) >= 0;
@@ -2492,8 +2667,8 @@ numeric_ge(PG_FUNCTION_ARGS)
 Datum
 numeric_lt(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) < 0;
@@ -2507,8 +2682,8 @@ numeric_lt(PG_FUNCTION_ARGS)
 Datum
 numeric_le(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 	bool		result;
 
 	result = cmp_numerics(num1, num2) <= 0;
@@ -2523,6 +2698,11 @@ static int
 cmp_numerics(Numeric num1, Numeric num2)
 {
 	int			result;
+	BufferedNumericVar var1;
+	BufferedNumericVar var2;
+
+	num1 = init_buffered_var_from_num(num1, &var1);
+	num2 = init_buffered_var_from_num(num2, &var2);
 
 	/*
 	 * We consider all NANs to be equal and larger than any non-NAN (including
@@ -2714,7 +2894,7 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
 Datum
 hash_numeric(PG_FUNCTION_ARGS)
 {
-	Numeric		key = PG_GETARG_NUMERIC(0);
+	Numeric		key = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
 	Datum		digit_hash;
 	Datum		result;
 	int			weight;
@@ -2723,6 +2903,9 @@ hash_numeric(PG_FUNCTION_ARGS)
 	int			i;
 	int			hash_len;
 	NumericDigit *digits;
+	BufferedNumericVar var;
+
+	key = init_buffered_var_from_num(key, &var);
 
 	/* If it's NaN or infinity, don't try to hash the rest of the fields */
 	if (NUMERIC_IS_SPECIAL(key))
@@ -2865,12 +3048,16 @@ hash_numeric_extended(PG_FUNCTION_ARGS)
 Datum
 numeric_add(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_add_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2882,11 +3069,15 @@ numeric_add(PG_FUNCTION_ARGS)
 Numeric
 numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+
+	BufferedNumericVar result;
 	Numeric		res;
 
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2915,18 +3106,15 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_ninf);
 	}
 
-	/*
-	 * Unpack the values, let add_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
 
-	init_var(&result);
-	add_var(&arg1, &arg2, &result);
+	add_var(&arg1.var, &arg2.var, &result.var);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -2940,12 +3128,16 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_sub(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_sub_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -2958,11 +3150,17 @@ numeric_sub(PG_FUNCTION_ARGS)
 Numeric
 numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let sub_var() compute the result and return it.
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -2991,18 +3189,14 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_pinf);
 	}
 
-	/*
-	 * Unpack the values, let sub_var() compute the result and return it.
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	sub_var(&arg1.var, &arg2.var, &result.var);
 
-	init_var(&result);
-	sub_var(&arg1, &arg2, &result);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3016,12 +3210,16 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_mul(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_mul_safe(num1, num2, fcinfo->context);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
 		PG_RETURN_NULL();
 
@@ -3037,11 +3235,25 @@ numeric_mul(PG_FUNCTION_ARGS)
 Numeric
 numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 
+	/*
+	 * Unpack the values, let mul_var() compute the result and return it.
+	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
+	 * case of numeric_mul(), which is invoked for the * operator on numerics,
+	 * we request exact representation for the product (rscale = sum(dscale of
+	 * arg1, dscale of arg2)).  If the exact result has more digits after the
+	 * decimal point than can be stored in a numeric, we round it.  Rounding
+	 * after computing the exact result ensures that the final result is
+	 * correctly rounded (rounding in mul_var() using a truncated product
+	 * would not guarantee this).
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3102,29 +3314,17 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 		Assert(false);
 	}
 
-	/*
-	 * Unpack the values, let mul_var() compute the result and return it.
-	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
-	 * case of numeric_mul(), which is invoked for the * operator on numerics,
-	 * we request exact representation for the product (rscale = sum(dscale of
-	 * arg1, dscale of arg2)).  If the exact result has more digits after the
-	 * decimal point than can be stored in a numeric, we round it.  Rounding
-	 * after computing the exact result ensures that the final result is
-	 * correctly rounded (rounding in mul_var() using a truncated product
-	 * would not guarantee this).
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
+	init_buffered_var(&result);
+	mul_var(&arg1.var, &arg2.var, &result.var, arg1.var.dscale + arg2.var.dscale);
 
-	init_var(&result);
-	mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
+	if (result.var.dscale > NUMERIC_DSCALE_MAX)
+		round_var(&result.var, NUMERIC_DSCALE_MAX);
 
-	if (result.dscale > NUMERIC_DSCALE_MAX)
-		round_var(&result, NUMERIC_DSCALE_MAX);
+	res = make_result_safe(&result.var, escontext);
 
-	res = make_result_safe(&result, escontext);
-
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 }
@@ -3138,12 +3338,16 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
 Datum
 numeric_div(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
+
 	Numeric		res;
 
 	res = numeric_div_safe(num1, num2, NULL);
 
+	PG_FREE_IF_COPY(num1, 0);
+	PG_FREE_IF_COPY(num2, 1);
+
 	PG_RETURN_NUMERIC(res);
 }
 
@@ -3156,12 +3360,18 @@ numeric_div(PG_FUNCTION_ARGS)
 Numeric
 numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 {
-	NumericVar	arg1;
-	NumericVar	arg2;
-	NumericVar	result;
+	BufferedNumericVar arg1;
+	BufferedNumericVar arg2;
+	BufferedNumericVar result;
 	Numeric		res;
 	int			rscale;
 
+	/*
+	 * Unpack the arguments
+	 */
+	num1 = init_buffered_var_from_num(num1, &arg1);
+	num2 = init_buffered_var_from_num(num2, &arg2);
+
 	/*
 	 * Handle NaN and infinities
 	 */
@@ -3209,31 +3419,27 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
 		return make_result(&const_zero);
 	}
 
-	/*
-	 * Unpack the arguments
-	 */
-	init_var_from_num(num1, &arg1);
-	init_var_from_num(num2, &arg2);
-
-	init_var(&result);
+	init_buffered_var(&result);
 
 	/*
 	 * Select scale for division result
 	 */
-	rscale = select_div_scale(&arg1, &arg2);
+	rscale = select_div_scale(&arg1.var, &arg2.var);
 
 	/* Check for division by zero */
-	if (arg2.ndigits == 0 || arg2.digits[0] == 0)
+	if (arg2.var.ndigits == 0 || arg2.var.digits[0] == 0)
 		goto division_by_zero;
 
 	/*
 	 * Do the divide and return the result
 	 */
-	div_var(&arg1, &arg2, &result, rscale, true, true);
+	div_var(&arg1.var, &arg2.var, &result.var, rscale, true, true);
 
-	res = make_result_safe(&result, escontext);
+	res = make_result_safe(&result.var, escontext);
 
-	free_var(&result);
+	free_var(&result.var);
+	free_var(&arg2.var);
+	free_var(&arg1.var);
 
 	return res;
 
@@ -3450,17 +3656,17 @@ numeric_inc(PG_FUNCTION_ARGS)
 Datum
 numeric_smaller(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) < 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -3472,17 +3678,17 @@ numeric_smaller(PG_FUNCTION_ARGS)
 Datum
 numeric_larger(PG_FUNCTION_ARGS)
 {
-	Numeric		num1 = PG_GETARG_NUMERIC(0);
-	Numeric		num2 = PG_GETARG_NUMERIC(1);
+	Numeric		num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
+	Numeric		num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
 
 	/*
 	 * Use cmp_numerics so that this will agree with the comparison operators,
 	 * particularly as regards comparisons involving NaN.
 	 */
 	if (cmp_numerics(num1, num2) > 0)
-		PG_RETURN_NUMERIC(num1);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
 	else
-		PG_RETURN_NUMERIC(num2);
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
 }
 
 
@@ -4854,7 +5060,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	state->N++;
 
 	/* Accumulate sums */
@@ -4862,7 +5067,6 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
 
 	if (state->calcSumX2)
 		accum_sum_add(&(state->sumX2), &X2);
-
 	MemoryContextSwitchTo(old_context);
 }
 
@@ -4932,7 +5136,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 			return false;
 		}
 	}
-
 	/* if we need X^2, calculate that in short-lived context */
 	if (state->calcSumX2)
 	{
@@ -4942,7 +5145,6 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 
 	/* The rest of this needs to work in the aggregate context */
 	old_context = MemoryContextSwitchTo(state->agg_context);
-
 	if (state->N-- > 1)
 	{
 		/* Negate X, to subtract it from the sum */
@@ -4960,14 +5162,11 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
 	{
 		/* Zero the sums */
 		Assert(state->N == 0);
-
 		accum_sum_reset(&state->sumX);
 		if (state->calcSumX2)
 			accum_sum_reset(&state->sumX2);
 	}
-
 	MemoryContextSwitchTo(old_context);
-
 	return true;
 }
 
@@ -6689,6 +6888,80 @@ dump_var(const char *str, NumericVar *var)
 
 	printf("\n");
 }
+
+
+static inline int
+check_digit_overflow(const NumericVar *var)
+{
+	int			i;
+
+	for (i = 0; i < var->ndigits; i++)
+	{
+		if (var->digits[i] >= NBASE)
+		{
+			return 1;
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * check_numericvar_state() -
+ *
+ * Validate internal NumericVar storage-state invariants.
+ *
+ * This is primarily intended as a debugging aid for future changes to the
+ * NumericVar storage-state machinery.
+ */
+static inline void
+check_numericvar_state(const NumericVar *var, const char *where)
+{
+	Assert(var != NULL);
+	Assert(var->ndigits >= 0);
+
+	if (var->state != NUMVAR_BORROWED)
+		Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->buf == NULL);
+			Assert(var->digits == NULL);
+			Assert(var->capacity == 0);
+			break;
+
+		case NUMVAR_BORROWED:
+			Assert(var->buf == NULL);
+			Assert(var->capacity == 0);
+			Assert(var->borrow_kind != NUMVAR_BORROW_NONE);
+
+			if (!NUMERICVAR_IS_SPECIAL(var))
+				Assert(var->digits != NULL);
+			break;
+
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->inline_buf != NULL);
+			Assert(var->buf == var->inline_buf);
+			Assert(var->digits != NULL);
+			Assert(var->capacity == NUMERIC_INLINE_BUFFER_SIZE);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->buf != NULL);
+			Assert(var->digits != NULL);
+			Assert(var->capacity > 0);
+			Assert(var->digits >= var->buf);
+			Assert(var->digits + var->ndigits <= var->buf + var->capacity);
+			break;
+
+		default:
+			elog(ERROR, "unexpected NumericVar state %d at %s",
+				 (int) var->state, where);
+	}
+}
 #endif							/* NUMERIC_DEBUG */
 
 
@@ -6703,35 +6976,296 @@ dump_var(const char *str, NumericVar *var)
  * ----------------------------------------------------------------------
  */
 
+/*
+ * digitbuf_get() -
+ *
+ * Ensure that a NumericVar has owned writable digit storage with at least the
+ * requested physical capacity, including any spare leading digit required by
+ * the caller.
+ *
+ * Regardless of the variable's current state, this function returns the base
+ * of writable owned storage and leaves the variable in either
+ * NUMVAR_OWNED_INLINE or NUMVAR_OWNED_HEAP state.
+ *
+ * This function is concerned only with storage acquisition and state
+ * transition.  Callers remain responsible for maintaining the logical numeric
+ * fields and for copying digit contents when required.
+ */
+static inline NumericDigit *
+digitbuf_get(NumericVar *var, int required_capacity)
+{
+	switch (var->state)
+	{
+		case NUMVAR_EMPTY:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf)
+			{
+				var->buf = var->inline_buf;
+				var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+				var->state = NUMVAR_OWNED_INLINE;
+			}
+			else
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_BORROWED:
+			if (var->borrow_kind == NUMVAR_BORROW_EXTERNAL)
+			{
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE && var->inline_buf != NULL)
+				{
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			else
+			{
+				Assert(var->inline_buf != NULL);
+				Assert(var->borrow_kind == NUMVAR_BORROW_INLINE);
+				if (required_capacity <= NUMERIC_INLINE_BUFFER_SIZE)
+				{
+					/*
+					 * NUMVAR_BORROW_INLINE is currently used only for the
+					 * special case where the inline buffer of a
+					 * BufferedNumericVar holds a detoasted Numeric datum.
+					 *
+					 * In this case, the caller guarantees that the borrowed
+					 * buffered representation does not need to be preserved
+					 * as an independent read-only view. Therefore, when the
+					 * requested capacity fits in the inline home, we may
+					 * convert that storage to NUMVAR_OWNED_INLINE by
+					 * resetting buf to the beginning of the inline buffer and
+					 * reestablishing the normal NumericVar working-buffer
+					 * layout there.
+					 */
+					var->buf = var->inline_buf;
+					var->capacity = NUMERIC_INLINE_BUFFER_SIZE;
+					var->state = NUMVAR_OWNED_INLINE;
+				}
+				else
+				{
+					var->buf = digitbuf_alloc_raw(required_capacity);
+					var->capacity = required_capacity;
+					var->state = NUMVAR_OWNED_HEAP;
+				}
+			}
+			break;
+		case NUMVAR_OWNED_INLINE:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			Assert(var->inline_buf != NULL);
+			if (required_capacity > var->capacity)
+			{
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+				var->state = NUMVAR_OWNED_HEAP;
+			}
+			break;
+		case NUMVAR_OWNED_HEAP:
+			Assert(var->borrow_kind == NUMVAR_BORROW_NONE);
+			if (required_capacity > var->capacity)
+			{
+				pfree(var->buf);
+				var->buf = digitbuf_alloc_raw(required_capacity);
+				var->capacity = required_capacity;
+			}
+			break;
+		default:
+			Assert(false);
+			return NULL;		/* silence compiler warning */
+	}
+
+	var->borrow_kind = NUMVAR_BORROW_NONE;
+	var->digits = var->buf + 1;
+
+	return var->buf;
+}
+
+
+/*
+ * digitbuf_free() -
+ *
+ * Release the current active digit storage or borrowed digit view of a
+ * NumericVar and return it to NUMVAR_EMPTY storage state.
+ *
+ * Heap-backed owned storage is pfree'd.  Inline owned storage is simply
+ * detached as the active buffer and remains available as the variable's
+ * inline home.  Borrowed storage is not freed, but any active borrowed view
+ * is dropped.
+ *
+ * This function resets only storage-state fields.  It does not by itself
+ * establish a meaningful numeric value.
+ */
+static inline void
+digitbuf_free(NumericVar *var)
+{
+	switch (var->state)
+	{
+		case NUMVAR_OWNED_HEAP:
+			pfree(var->buf);
+			pg_fallthrough;
+		case NUMVAR_EMPTY:
+		case NUMVAR_BORROWED:
+		case NUMVAR_OWNED_INLINE:
+			var->buf = NULL;
+			var->capacity = 0;
+			var->digits = NULL;
+			var->state = NUMVAR_EMPTY;
+			var->borrow_kind = NUMVAR_BORROW_NONE;
+			break;
+		default:
+			Assert(false);
+	}
+}
+
+
+/*
+ * result_aliases_input() -
+ *
+ * Return true if the destination variable cannot be used directly as the
+ * output of an arithmetic operation because it aliases one of the inputs.
+ *
+ * In addition to direct pointer equality, this treats result as aliasing if
+ * its current digit pointer falls within the active digit range of either
+ * input variable.
+ */
+static bool
+result_aliases_input(const NumericVar *result,
+					 const NumericVar *a,
+					 const NumericVar *b)
+{
+	if (result == a || result == b)
+		return true;
+
+	/*
+	 * Be conservative: if result currently points into either input digit
+	 * range, treat it as aliasing as well.
+	 */
+	if (result->digits != NULL)
+	{
+		if (a->digits != NULL &&
+			result->digits >= a->digits &&
+			result->digits < a->digits + a->ndigits)
+			return true;
+
+		if (b->digits != NULL &&
+			result->digits >= b->digits &&
+			result->digits < b->digits + b->ndigits)
+			return true;
+	}
+
+	return false;
+}
+
+
+/*
+ * prepare_result_var() -
+ *
+ * Prepare a writable result variable for an arithmetic operation.
+ *
+ * If the caller's destination does not alias either input, writable storage
+ * is allocated directly in that destination and the destination is returned.
+ *
+ * If the destination aliases an input, a temporary BufferedNumericVar is
+ * initialized and allocated instead, so that the operation may read its
+ * inputs without destroying their digit storage.
+ */
+static NumericVar *
+prepare_result_var(NumericVar *result,
+				   const NumericVar *a,
+				   const NumericVar *b,
+				   int res_ndigits,
+				   BufferedNumericVar * tmp)
+{
+	if (result_aliases_input(result, a, b))
+	{
+		init_buffered_var(tmp);
+
+		alloc_var(&tmp->var, res_ndigits);
+
+		return &tmp->var;
+	}
+
+	alloc_var(result, res_ndigits);
+
+	return result;
+}
+
+
+/*
+ * finish_result_var() -
+ *
+ * Finalize a result prepared by prepare_result_var().
+ *
+ * If the arithmetic result was computed directly in the caller's destination,
+ * this function does nothing.
+ *
+ * If the result was computed in a temporary variable, copy that value back
+ * into the caller's destination and release the temporary storage.
+ */
+static void
+finish_result_var(NumericVar *work_result, NumericVar *result)
+{
+	if (work_result != result)
+	{
+		set_var_from_var(work_result, result);
+		free_var(work_result);
+	}
+}
+
 
 /*
  * alloc_var() -
  *
- *	Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
+ * Allocate owned writable digit storage for a NumericVar large enough for the
+ * requested number of logical digits plus one spare leading digit.
+ *
+ * The variable is left in an owned writable state with basic working metadata
+ * initialized, but callers remain responsible for constructing the final
+ * numeric value.
  */
 static void
 alloc_var(NumericVar *var, int ndigits)
 {
-	digitbuf_free(var->buf);
-	var->buf = digitbuf_alloc(ndigits + 1);
-	var->buf[0] = 0;			/* spare digit for rounding */
-	var->digits = var->buf + 1;
+	digitbuf_free(var);
+
+	digitbuf_get(var, ndigits + 1);
+
 	var->ndigits = ndigits;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
+	var->buf[0] = 0;			/* ensure spare digit is zeroed */
 }
 
 
 /*
  * free_var() -
  *
- *	Return the digit buffer of a variable to the free pool
+ * Release the current active storage of a NumericVar and reset its numeric
+ * metadata to an empty working state.
+ *
+ * Unlike digitbuf_free(), this is a variable-level cleanup helper: it clears
+ * both storage-state and value-level fields.
  */
 static void
 free_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
-	var->buf = NULL;
-	var->digits = NULL;
-	var->sign = NUMERIC_NAN;
+	/* Release current active storage, if any. */
+	digitbuf_free(var);
+
+	var->ndigits = 0;
+	var->weight = 0;
+	var->sign = NUMERIC_POS;
+	var->dscale = 0;
 }
 
 
@@ -6744,7 +7278,7 @@ free_var(NumericVar *var)
 static void
 zero_var(NumericVar *var)
 {
-	digitbuf_free(var->buf);
+	free_var(var);
 	var->buf = NULL;
 	var->digits = NULL;
 	var->ndigits = 0;
@@ -6923,9 +7457,11 @@ set_var_from_str(const char *str, const char *cp,
 	ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
 	dest->sign = sign;
 	dest->weight = weight;
 	dest->dscale = dscale;
+	dest->digits = dest->buf + 1;
 
 	i = DEC_DIGITS - offset;
 	digits = dest->digits;
@@ -7184,6 +7720,8 @@ set_var_from_num(Numeric num, NumericVar *dest)
 	ndigits = NUMERIC_NDIGITS(num);
 
 	alloc_var(dest, ndigits);
+	dest->ndigits = ndigits;
+	dest->digits = dest->buf + 1;
 
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
@@ -7196,50 +7734,188 @@ set_var_from_num(Numeric num, NumericVar *dest)
 /*
  * init_var_from_num() -
  *
- *	Initialize a variable from packed db format. The digits array is not
- *	copied, which saves some cycles when the resulting var is not modified.
- *	Also, there's no need to call free_var(), as long as you don't assign any
- *	other value to it (with set_var_* functions, or by using the var as the
- *	destination of a function like add_var())
+ * Initialize a NumericVar from an input Numeric datum, detoasting the datum
+ * first if necessary.
  *
- *	CAUTION: Do not modify the digits buffer of a var initialized with this
- *	function, e.g by calling round_var() or trunc_var(), as the changes will
- *	propagate to the original Numeric! It's OK to use it as the destination
- *	argument of one of the calculational functions, though.
+ * For finite values, the resulting NumericVar is a borrowed read-only view of
+ * externally owned digit storage, with borrow kind
+ * NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the detoasted representation actually used
+ * to initialize the borrowed view.
  */
-static void
+static Numeric
 init_var_from_num(Numeric num, NumericVar *dest)
 {
+	/* ensure the numeric is always detoasted */
+	num = (Numeric) PG_DETOAST_DATUM(NumericGetDatum(num));
+
+	init_var(dest);
+
 	dest->ndigits = NUMERIC_NDIGITS(num);
 	dest->weight = NUMERIC_WEIGHT(num);
 	dest->sign = NUMERIC_SIGN(num);
 	dest->dscale = NUMERIC_DSCALE(num);
-	dest->digits = NUMERIC_DIGITS(num);
-	dest->buf = NULL;			/* digits array is not palloc'd */
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->state = NUMVAR_EMPTY;
+		dest->borrow_kind = NUMVAR_BORROW_NONE;
+	}
+	else
+	{
+		dest->state = NUMVAR_BORROWED;
+		dest->borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+		dest->digits = NUMERIC_DIGITS(num);
+	}
+
+	return num;
+}
+
+
+/*
+ * init_buffered_var_from_num() -
+ *
+ * Initialize a BufferedNumericVar from an input Numeric datum, using the
+ * object's inline buffer as detoast workspace when possible.
+ *
+ * For finite values, the resulting NumericVar is a borrowed read-only view.
+ * If the detoasted representation is materialized in the inline buffer, the
+ * borrow kind is recorded as NUMVAR_BORROW_INLINE; otherwise it is recorded
+ * as NUMVAR_BORROW_EXTERNAL.
+ *
+ * For special values, no digit storage is borrowed; the resulting NumericVar
+ * remains in NUMVAR_EMPTY storage state, and only sign is semantically
+ * meaningful.
+ *
+ * The returned Numeric pointer is the representation actually used to
+ * initialize the borrowed view.
+ */
+static Numeric
+init_buffered_var_from_num(Numeric num, BufferedNumericVar * dest)
+{
+	num = (Numeric) numeric_detoast_attr_to_buffer((varlena *) (num),
+												   (unsigned char *) dest->buffer,
+												   sizeof(dest->buffer));
+
+	if (NUMERIC_IS_SPECIAL(num))
+	{
+		dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+		init_var(&dest->var);
+		dest->var.inline_buf = dest->buffer;
+		dest->var.sign = NUMERIC_SIGN(num);
+		return num;
+	}
+
+	dest->magic = BUFFERED_NUMERIC_VAR_MAGIC;
+	init_var(&dest->var);
+	dest->var.inline_buf = dest->buffer;
+
+	dest->var.state = NUMVAR_BORROWED;
+	dest->var.ndigits = NUMERIC_NDIGITS(num);
+	dest->var.weight = NUMERIC_WEIGHT(num);
+	dest->var.sign = NUMERIC_SIGN(num);
+	dest->var.dscale = NUMERIC_DSCALE(num);
+	dest->var.digits = NUMERIC_DIGITS(num);
+
+	if ((unsigned char *) num == (unsigned char *) dest->buffer)
+		dest->var.borrow_kind = NUMVAR_BORROW_INLINE;
+	else
+		dest->var.borrow_kind = NUMVAR_BORROW_EXTERNAL;
+
+	return num;
+}
+
+
+/*
+ * numeric_detoast_attr_to_buffer() -
+ *
+ * Convert a varlena datum to a detoasted representation, preferably using a
+ * caller-provided buffer when that is sufficient.
+ *
+ * This helper is intended for numeric paths.  If the input datum is in
+ * short-header form, it is expanded to ordinary 4-byte-header form, using
+ * the supplied buffer when possible and falling back to palloc'd storage
+ * otherwise.
+ *
+ * If the input datum is not in short-header form, this helper falls back to
+ * detoast_attr().
+ *
+ * The returned pointer may therefore refer either to the caller-provided
+ * buffer or to freshly allocated storage.
+ */
+static varlena *
+numeric_detoast_attr_to_buffer(varlena *attr, unsigned char *buffer,
+							   size_t buffer_size)
+{
+	Size		data_size;
+	Size		new_size;
+	varlena    *new_attr;
+
+	if (!VARATT_IS_SHORT(attr))
+		return pg_detoast_datum(attr);
+
+	/*
+	 * This is a short-header varlena --- convert to 4-byte header format
+	 */
+	data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;
+	new_size = data_size + VARHDRSZ;
+
+	if (unlikely(buffer_size < new_size))
+		new_attr = (varlena *) palloc(new_size);
+	else
+		new_attr = (varlena *) buffer;
+	SET_VARSIZE(new_attr, new_size);
+	memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);
+	attr = new_attr;
+
+	return attr;
 }
 
 
 /*
  * set_var_from_var() -
  *
- *	Copy one variable into another
+ * Copy one NumericVar into another.
+ *
+ * The destination receives the same numeric value as the source, but this
+ * function always constructs a destination representation that is valid for
+ * the destination's own storage state.  It does not steal storage from the
+ * source.
+ *
+ * This function must remain safe when source and destination are the same
+ * variable.
  */
 static void
 set_var_from_var(const NumericVar *value, NumericVar *dest)
 {
-	NumericDigit *newbuf;
+	if (value == dest)
+		return;
 
-	newbuf = digitbuf_alloc(value->ndigits + 1);
-	newbuf[0] = 0;				/* spare digit for rounding */
-	if (value->ndigits > 0)		/* else value->digits might be null */
-		memcpy(newbuf + 1, value->digits,
-			   value->ndigits * sizeof(NumericDigit));
+	if (NUMERICVAR_IS_SPECIAL(value))
+	{
+		free_var(dest);
+		dest->sign = value->sign;
+
+		return;
+	}
 
-	digitbuf_free(dest->buf);
+	/* ndigits is set in alloc_var */
+	alloc_var(dest, value->ndigits);
 
-	memmove(dest, value, sizeof(NumericVar));
-	dest->buf = newbuf;
-	dest->digits = newbuf + 1;
+	dest->weight = value->weight;
+	dest->dscale = value->dscale;
+	dest->sign = value->sign;
+
+	if (value->ndigits > 0)
+		memcpy(dest->digits,
+			value->digits,
+			value->ndigits * sizeof(NumericDigit));
 }
 
 
@@ -8600,6 +9276,7 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
 	 */
 	result->weight = res_weight;
 	result->sign = res_sign;
+	result->ndigits = res_ndigits;
 
 	/* Round to target rscale (and set result->dscale) */
 	round_var(result, rscale);
@@ -8631,6 +9308,8 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	NumericDigit *res_digits;
 	uint32		carry = 0;
 	uint32		term;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Check preconditions */
 	Assert(var1ndigits >= 1);
@@ -8651,8 +9330,9 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	res_weight = var1->weight + var2->weight + 1;
 	res_ndigits = var1ndigits + var2ndigits;
 
-	/* Allocate result digit array */
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -8872,16 +9552,17 @@ mul_var_short(const NumericVar *var1, const NumericVar *var2,
 	}
 
 	/* Store the product in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
-	result->dscale = var1->dscale + var2->dscale;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = var1->dscale + var2->dscale;
 
 	/* Strip leading and trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -9457,6 +10138,8 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint32		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9490,7 +10173,9 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9532,12 +10217,14 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -9573,6 +10260,8 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	NumericDigit *res_digits;
 	uint64		divisor;
 	int			i;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* Guard against division by zero */
 	if (ival == 0)
@@ -9606,7 +10295,9 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	if (round)
 		res_ndigits++;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var, var, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -9648,12 +10339,13 @@ div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
 	}
 
 	/* Store the quotient in result */
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->sign = res_sign;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->sign = res_sign;
+	res->dscale = rscale;
+
+	finish_result_var(res, result);
 
 	/* Round or truncate to target rscale (and set result->dscale) */
 	if (round)
@@ -11494,6 +12186,8 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			carry = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11514,7 +12208,9 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11543,15 +12239,15 @@ add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(carry == 0);			/* else we failed to allow for carry out */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 
@@ -11579,6 +12275,8 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 				i1,
 				i2;
 	int			borrow = 0;
+	BufferedNumericVar tmp;
+	NumericVar *res;
 
 	/* copy these values into local vars for speed in inner loop */
 	int			var1ndigits = var1->ndigits;
@@ -11599,7 +12297,9 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 	if (res_ndigits <= 0)
 		res_ndigits = 1;
 
-	res_buf = digitbuf_alloc(res_ndigits + 1);
+	res = prepare_result_var(result, var1, var2, res_ndigits, &tmp);
+
+	res_buf = res->buf;
 	res_buf[0] = 0;				/* spare digit for later rounding */
 	res_digits = res_buf + 1;
 
@@ -11628,15 +12328,15 @@ sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
 
 	Assert(borrow == 0);		/* else caller gave us var1 < var2 */
 
-	digitbuf_free(result->buf);
-	result->ndigits = res_ndigits;
-	result->buf = res_buf;
-	result->digits = res_digits;
-	result->weight = res_weight;
-	result->dscale = res_dscale;
+	res->ndigits = res_ndigits;
+	res->digits = res_digits;
+	res->weight = res_weight;
+	res->dscale = res_dscale;
 
 	/* Remove leading/trailing zeroes */
-	strip_var(result);
+	strip_var(res);
+
+	finish_result_var(res, result);
 }
 
 /*
@@ -12099,7 +12799,9 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 
 	/* Create NumericVars representing the positive and negative sums */
 	init_var(&pos_var);
+	alloc_var(&pos_var, accum->ndigits);
 	init_var(&neg_var);
+	alloc_var(&neg_var, accum->ndigits);
 
 	pos_var.ndigits = neg_var.ndigits = accum->ndigits;
 	pos_var.weight = neg_var.weight = accum->weight;
@@ -12107,8 +12809,8 @@ accum_sum_final(NumericSumAccum *accum, NumericVar *result)
 	pos_var.sign = NUMERIC_POS;
 	neg_var.sign = NUMERIC_NEG;
 
-	pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits);
-	neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits);
+	pos_var.buf = pos_var.digits = digitbuf_alloc_raw(accum->ndigits);
+	neg_var.buf = neg_var.digits = digitbuf_alloc_raw(accum->ndigits);
 
 	for (i = 0; i < accum->ndigits; i++)
 	{
-- 
2.43.0



  [application/octet-stream] 0002-numeric-add-regression-coverage-for-NumericVar-state.patch (131.9K, ../../[email protected]/4-0002-numeric-add-regression-coverage-for-NumericVar-state.patch)
  download | inline diff:
From 962dac874e371cce5eececad482d3c0fa512308b Mon Sep 17 00:00:00 2001
From: sohardforaname <[email protected]>
Date: Sun, 10 May 2026 21:43:50 +0800
Subject: [PATCH 2/2] numeric: add regression coverage for NumericVar state
 changes

Add regression coverage for the NumericVar storage-state refactoring.

The tests cover a wide range of numeric typmods, including high-precision
and high-scale cases, and exercise arithmetic, composite expressions,
comparison-sensitive paths, window functions, join-driven expressions,
aggregates, updates, error cases, and index/index-only related paths.

This is intended to verify that the refactoring preserves SQL-visible
behavior across both ordinary and extreme numeric workloads.
---
 src/test/regress/expected/numeric_op_big.out  | 487 ++++++++++++++
 src/test/regress/expected/numeric_op_ddl.out  | 167 +++++
 .../regress/expected/numeric_op_medium.out    | 487 ++++++++++++++
 .../regress/expected/numeric_op_small.out     | 636 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   4 +
 src/test/regress/sql/numeric_op_big.sql       | 269 ++++++++
 src/test/regress/sql/numeric_op_ddl.sql       | 173 +++++
 src/test/regress/sql/numeric_op_medium.sql    | 269 ++++++++
 src/test/regress/sql/numeric_op_small.sql     | 335 +++++++++
 9 files changed, 2827 insertions(+)
 create mode 100644 src/test/regress/expected/numeric_op_big.out
 create mode 100644 src/test/regress/expected/numeric_op_ddl.out
 create mode 100644 src/test/regress/expected/numeric_op_medium.out
 create mode 100644 src/test/regress/expected/numeric_op_small.out
 create mode 100644 src/test/regress/sql/numeric_op_big.sql
 create mode 100644 src/test/regress/sql/numeric_op_ddl.sql
 create mode 100644 src/test/regress/sql/numeric_op_medium.sql
 create mode 100644 src/test/regress/sql/numeric_op_small.sql

diff --git a/src/test/regress/expected/numeric_op_big.out b/src/test/regress/expected/numeric_op_big.out
new file mode 100644
index 00000000000..be0c51a9b2e
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_big.out
@@ -0,0 +1,487 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -93674011458
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 49431437020
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | 81596504540
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum      |     sum     |     sum     |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+-------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 103787738890 | 71241136711 | 91238480326 | -25880446966 | 85861504452 | 138111163668 | -64934216831 | -3883271108300 | -3883271108300 | -7965490469700 | -263811485216 | -196222417055 | -253470347100
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+      sum      |     sum      |     sum     |     sum      |      sum      |     sum      |     sum      |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+---------------+--------------+-------------+--------------+---------------+--------------+--------------+----------------+----------------+----------------+---------------+---------------+---------------
+ -168835944407 | -37026039983 | -9162875272 | 103006726746 | -108905948664 | -77309522276 | 146591765000 | -3883271108300 | -3883271108300 | -7965490469700 | -111504876460 | -183625584196 | -243750883157
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |     sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+--------------+--------------
+ 81596504540 | 81596504540 | -448199909000 |     |     | -448199909000 | 81596504540 | 153330623000 | -1995205883000 | -1995205883000 | 81596504540 | -70370694025 | -81922915700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -153072055232
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -221944827430
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ -87303812940 | -87303812940 | -36200000898
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1dab074815e4209082cd0fff9582bf2c | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 1a5a255cc4b0646f8cb27448a2df2310 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ f3944f455b5fc4deb1eb3c782e38b134 | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_800 on t_1000_800
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_995 on t_1000_995
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Index Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using idx_t_1000_1000 on t_1000_1000
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 187915496731
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+ count |      sum      
+-------+---------------
+  2001 | -120277609243
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+ count |     sum     
+-------+-------------
+   200 | -6791326615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+     sum     |     sum      |     sum     |    sum     |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum      |     sum      |     sum      
+-------------+--------------+-------------+------------+-------------+-------------+-------------+----------------+----------------+----------------+--------------+--------------+--------------
+ 69378299805 | -28004625396 | 69165702075 | 6495822165 | 42794676167 | 28778131261 | 23748489343 | -3668417457700 | -3883271108300 | -8180344120300 | 162788189344 | -88035415965 | -14300392972
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+     sum      |    sum    |     sum      |     sum      |     sum      |    sum     |     sum      |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+-----------+--------------+--------------+--------------+------------+--------------+----------------+----------------+----------------+---------------+--------------+---------------
+ 106142378501 | 456865251 | -40344563826 | -82057607022 | -74296237953 | 4474319379 | 111919427572 | -3668417457700 | -3883271108300 | -8180344120300 | -223137274026 | -60159164731 | -114299102966
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+     sum     |     sum     |      sum      | sum | sum |      sum      |     sum     |     sum      |      sum       |      sum       |     sum     |      sum      |     sum      
+-------------+-------------+---------------+-----+-----+---------------+-------------+--------------+----------------+----------------+-------------+---------------+--------------
+ -6791326615 | -6791326615 | -448199909000 |     |     | -448199909000 | -6791326615 | 153330623000 | -1995205883000 | -1995205883000 | -6791326615 | -116888674645 | -70370694025
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -23880525307
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 31543863976
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ -87303812940 | -87303812940 | 18056325685
+(1 row)
+
+select a/0 from t_1000_800;
+ERROR:  division by zero
+select a/0 from t_1000_995;
+ERROR:  division by zero
+select a/0 from t_1000_1000;
+ERROR:  division by zero
+select a%0 from t_1000_800;
+ERROR:  division by zero
+select a%0 from t_1000_995;
+ERROR:  division by zero
+select a%0 from t_1000_1000;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_800;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_995;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_1000;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 9af0d5833a5dc496ac4cd53bca6c8602 | 5d0bb1be149c34037fddfade54a3adf6 | 6462f616299e10ce9d30f1a9d1bbeb20
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 11cbb4649046194d3a521054a6e9d2f1 | 3c1165aed3ac2fe402386fd8bc6a7f69 | bb44d6c91ac3138f7712d7062ba1d4c0
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a76cd728b4a258a8353a3288df6fa78c | 1a5a255cc4b0646f8cb27448a2df2310 | 82295228238aae9f4eb3e7e71bb3b1d0
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+     sum      
+--------------
+ -27571036926
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+     sum      
+--------------
+ 142042826843
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
+     sum      
+--------------
+ -37293091674
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_ddl.out b/src/test/regress/expected/numeric_op_ddl.out
new file mode 100644
index 00000000000..d8a1523a5fc
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_ddl.out
@@ -0,0 +1,167 @@
+DROP TABLE IF EXISTS t_9_2;
+NOTICE:  table "t_9_2" does not exist, skipping
+DROP TABLE IF EXISTS t_18_2;
+NOTICE:  table "t_18_2" does not exist, skipping
+DROP TABLE IF EXISTS t_38_2;
+NOTICE:  table "t_38_2" does not exist, skipping
+DROP TABLE IF EXISTS t_80_16;
+NOTICE:  table "t_80_16" does not exist, skipping
+DROP TABLE IF EXISTS t_175_35;
+NOTICE:  table "t_175_35" does not exist, skipping
+DROP TABLE IF EXISTS t_400_80;
+NOTICE:  table "t_400_80" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_200;
+NOTICE:  table "t_1000_200" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_800;
+NOTICE:  table "t_1000_800" does not exist, skipping
+DROP TABLE IF EXISTS t_1000_995;
+NOTICE:  table "t_1000_995" does not exist, skipping
+DROP TABLE IF EXISTS t_base;
+NOTICE:  table "t_base" does not exist, skipping
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+DROP TABLE IF EXISTS t_1000_1000;
+NOTICE:  table "t_1000_1000" does not exist, skipping
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+VACUUM FREEZE ANALYZE t_1000_1000;
diff --git a/src/test/regress/expected/numeric_op_medium.out b/src/test/regress/expected/numeric_op_medium.out
new file mode 100644
index 00000000000..bf4655f59bd
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_medium.out
@@ -0,0 +1,487 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -41380769803
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -17147548455
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -87880682140
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum      |     sum      |     sum     |     sum     |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+--------------+-------------+-------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 328096480219 | 106894971180 | 29998131312 | 16824177090 | -98601154544 | -84446300842 | -142367027518 | -3883271108300 | -3883271108300 | -7965490469700 | -31882620402 | -214909270261 | -312201369170
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |      sum      |     sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |     sum      |      sum      
+--------------+---------------+--------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+---------------+--------------+---------------
+ -89713139799 | -218213819552 | 100513273638 | -79355358716 | 45044419840 | -3009527768 | 27142855016 | -3883271108300 | -3883271108300 | -7965490469700 | -248734189206 | -85072914373 | -210488126609
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+    sum    |     sum      |     sum      |     sum      |     sum      |     sum     |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+-----------+--------------+--------------+--------------+--------------+-------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 534102535 | 222800148124 | 252518296200 | -40723661604 | -53446320231 | 49858073732 | 23224917403 | -3883271108300 | -3883271108300 | -7965490469700 | -212735400480 | -257086925922 | -206858526458
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 73062391064
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | 5986675256
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -10341341254
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 656fe5126aca905220a5f1f28a9f4e4a | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ a2c9fcd02be06405b103ae3e0caa69cd | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6bdaa86b859269f0b1de287472e010c6 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+                QUERY PLAN                 
+-------------------------------------------
+ Index Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_175_35 on t_175_35
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+                QUERY PLAN                 
+-------------------------------------------
+ Index Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using idx_t_400_80 on t_400_80
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Index Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using idx_t_1000_200 on t_1000_200
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -89558437612
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | -19722210906
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 277783880994
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+     sum     |     sum      |     sum      |     sum     |      sum      |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |     sum      
+-------------+--------------+--------------+-------------+---------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+--------------
+ 48377864776 | -49822309848 | 178050220784 | 16824177090 | -138513601794 | 194342415281 | 28892192658 | -3668417457700 | -3883271108300 | -8180344120300 | -151559852522 | -152028559294 | -63268316355
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+     sum      |     sum      |     sum      |     sum      |     sum     |     sum      |     sum      |      sum       |      sum       |      sum       |     sum      |     sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+--------------+--------------+----------------+----------------+----------------+--------------+--------------+---------------
+ -67452832312 | -89225471931 | 146580491810 | -79355358716 | -9287051683 | 147260222842 | -70335569319 | -3668417457700 | -3883271108300 | -8180344120300 | -32938146279 | -59204444070 | -108234520264
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+      sum      |      sum      |    sum    |     sum      |     sum      |     sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+-----------+--------------+--------------+--------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -163275209177 | -193968650050 | 679620265 | -40723661604 | -40601882101 | 147411738034 | -193122653902 | -3668417457700 | -3883271108300 | -8180344120300 | 270045436866 | -108867098352 | -172351504959
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -35696475290
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -203092149118
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -75070192124
+(1 row)
+
+select a/0 from t_175_35;
+ERROR:  division by zero
+select a/0 from t_400_80;
+ERROR:  division by zero
+select a/0 from t_1000_200;
+ERROR:  division by zero
+select a%0 from t_175_35;
+ERROR:  division by zero
+select a%0 from t_400_80;
+ERROR:  division by zero
+select a%0 from t_1000_200;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_175_35;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_400_80;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_1000_200;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 0a0a2e6cda8bed8777ff796691554d3c | fab6538b49575bb053d186e72c71780b | 291c51941460f638c764620bf0ced7d9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ ab6a736e3d9a9c028b565f452d3f8f2f | fd0e670d64fcfe8f207ee4e2e721343b | bf371cdb26b1f75fb7ed2a87a68ac6d3
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ cbbfd005c1274880a47452997697e936 | 6e03994c568f38b30983abf096c4e7a2 | b3b8d5cca8a957ee6b072786cd995c29
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+     sum      
+--------------
+ -27666707523
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+     sum     
+-------------
+ 68424688419
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
+     sum      
+--------------
+ -58373747124
+(1 row)
+
diff --git a/src/test/regress/expected/numeric_op_small.out b/src/test/regress/expected/numeric_op_small.out
new file mode 100644
index 00000000000..45b511b8ff5
--- /dev/null
+++ b/src/test/regress/expected/numeric_op_small.out
@@ -0,0 +1,636 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 25264177945
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum      
+-------+--------------
+  2000 | -18318735365
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 77086657878
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum     
+-------+-------------
+  2000 | 51102838669
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+     sum      |     sum     |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |      sum      |      sum      |      sum      
+--------------+-------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+---------------+---------------+---------------
+ 185332843643 | 74295396054 | -265653763404 | -130951372896 | 33926686039 | -93220527164 | 53982021999 | -3883271108300 | -3883271108300 | -7965490469700 | -136748774422 | -156349489824 | -229516261618
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+     sum      |    sum     |     sum      |      sum      |      sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+------------+--------------+---------------+---------------+---------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 205522402955 | 1737491244 | -98734378124 | -156012333362 | -122318628351 | -265348319222 | 53131151046 | -3883271108300 | -3883271108300 | -7965490469700 | -74716919418 | -132260756970 | -267366558171
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+     sum      |     sum      |      sum      |      sum      |     sum      |     sum      |     sum      |      sum       |      sum       |      sum       |    sum     |      sum      |      sum      
+--------------+--------------+---------------+---------------+--------------+--------------+--------------+----------------+----------------+----------------+------------+---------------+---------------
+ 188179625901 | 112456755410 | -343776078076 | -174825681708 | -71003178193 | 142439459768 | 114154703061 | -3883271108300 | -3883271108300 | -7965490469700 | 8740136096 | -345863766775 | -368207427216
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum     |      sum      |     sum      |      sum      |      sum      |      sum      |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+--------------+-------------+---------------+--------------+---------------+---------------+---------------+----------------+----------------+----------------+--------------+---------------+---------------
+ 159659505470 | -1983944819 | -319417093044 | -11104453418 | -158100462876 | -145951162894 | -124997818319 | -3883271108300 | -3883271108300 | -7965490469700 | 146077391644 | -208253897723 | -211323905998
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 47811429266
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -17989105700
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -79591213532
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |      sum      
+--------------+--------------+---------------
+ 122592282760 | 122592282760 | -190878347020
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 01e08d38c9dbee1eacb6a7ac987a5cb8 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ bdef27f6ac65bf7ef274cde752e40409 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+             QUERY PLAN              
+-------------------------------------
+ Index Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using idx_t_9_2 on t_9_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_18_2 on t_18_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+              QUERY PLAN               
+---------------------------------------
+ Index Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+                 QUERY PLAN                 
+--------------------------------------------
+ Index Only Scan using idx_t_38_2 on t_38_2
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+               QUERY PLAN                
+-----------------------------------------
+ Index Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using idx_t_80_16 on t_80_16
+   Index Cond: (a > '1000'::numeric)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 55192112654
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+ count |     sum     
+-------+-------------
+  2001 | 79276791029
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 119235302735
+(1 row)
+
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+ count |     sum      
+-------+--------------
+  2001 | 109095039015
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+      sum      |      sum      |      sum      |     sum      |     sum     |     sum     |     sum     |      sum       |      sum       |      sum       |     sum     |     sum      |     sum      
+---------------+---------------+---------------+--------------+-------------+-------------+-------------+----------------+----------------+----------------+-------------+--------------+--------------
+ -198398285973 | -268636739265 | -185796812937 | -80212568293 | 40366812062 | 50022231011 | 86359907719 | -3668417457700 | -3883271108300 | -8180344120300 | 88627277998 | -83578376254 | -56047555653
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+      sum      |      sum      |      sum      |     sum      |     sum      |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+---------------+---------------+---------------+--------------+--------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ -198076509004 | -185931021498 | -174261325669 | -62721548562 | -87570938488 | -151149981174 | 46905358609 | -3668417457700 | -3883271108300 | -8180344120300 | 66791978874 | -162795673535 | -140859793615
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+      sum      |      sum      |      sum      |      sum      |     sum     |     sum      |     sum     |      sum       |      sum       |      sum       |     sum      |      sum      |      sum      
+---------------+---------------+---------------+---------------+-------------+--------------+-------------+----------------+----------------+----------------+--------------+---------------+---------------
+ -173598401621 | -191141226891 | -153991008984 | -174825681708 | -9994590731 | 103039770436 | 90917337737 | -3668417457700 | -3883271108300 | -8180344120300 | 212488760840 | -157402192456 | -222636044392
+(1 row)
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+     sum      |     sum      |     sum      |     sum      |     sum     |      sum      |     sum     |      sum       |      sum       |      sum       |     sum     |      sum      |      sum      
+--------------+--------------+--------------+--------------+-------------+---------------+-------------+----------------+----------------+----------------+-------------+---------------+---------------
+ 179235436643 | 248686171421 | -90086495035 | -11104453418 | 11551494962 | -229139601272 | 32577271698 | -3668417457700 | -3883271108300 | -8180344120300 | 81696122255 | -125106925825 | -150252904102
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+     sum      |     sum      |     sum      
+--------------+--------------+--------------
+ 122592282760 | 122592282760 | -13202507172
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+     sum      |     sum      |    sum     
+--------------+--------------+------------
+ 122592282760 | 122592282760 | -696258940
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | 19679287493
+(1 row)
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+     sum      |     sum      |     sum     
+--------------+--------------+-------------
+ 122592282760 | 122592282760 | -6889081579
+(1 row)
+
+select a/0 from t_9_2;
+ERROR:  division by zero
+select a/0 from t_18_2;
+ERROR:  division by zero
+select a/0 from t_38_2;
+ERROR:  division by zero
+select a/0 from t_80_16;
+ERROR:  division by zero
+select a%0 from t_9_2;
+ERROR:  division by zero
+select a%0 from t_18_2;
+ERROR:  division by zero
+select a%0 from t_38_2;
+ERROR:  division by zero
+select a%0 from t_80_16;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_9_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_18_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_38_2;
+ERROR:  division by zero
+select (a-a)/(a-a) from t_80_16;
+ERROR:  division by zero
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e819a237bbf437ea1758200e1593f4b | cbe8801ed9df407fd42bd90172ac605c
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | 2e927e5aa2c1897e19ceaebbea8ef352 | 233ddd11705b908f63547b2ba13c71b9
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 517d9317db153749523deef82aa8a952 | a9bc61161e087c32a19ac2e48da03707 | ccfc186273325fb6fc75d6dbfcbb6885
+(1 row)
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+               md5                |               md5                |               md5                
+----------------------------------+----------------------------------+----------------------------------
+ 6f9a5a137cb8760eaf667bc7353b0f82 | dd3a2591160b367edfb70c3ed7f78082 | 3e076e97431eff6fccc15a7f77610928
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+     sum      
+--------------
+ -16440552090
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+     sum      
+--------------
+ 104998257177
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+    sum     
+------------
+ 9189839531
+(1 row)
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
+      sum      
+---------------
+ -126456520242
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..926010b47cb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -146,3 +146,7 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# this test is to check if numeric operations are correct
+test: numeric_op_ddl
+test: numeric_op_small numeric_op_medium numeric_op_big
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_big.sql b/src/test/regress/sql/numeric_op_big.sql
new file mode 100644
index 00000000000..b8cff14fa93
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_big.sql
@@ -0,0 +1,269 @@
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_1000_800 where a > 1000 order by a;
+explain (costs off) select a from t_1000_800 where a > 1000 order by a;
+explain (costs off) select * from t_1000_995 where a > 1000 order by a;
+explain (costs off) select a from t_1000_995 where a > 1000 order by a;
+explain (costs off) select * from t_1000_1000 where a > 1000 order by a;
+explain (costs off) select a from t_1000_1000 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_1000_800 set a = a + 1;
+update t_1000_995 set a = a + 1;
+update t_1000_1000 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_1000_800 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_995 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_1000 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_800;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_995;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_1000;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_800
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_995
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_1000
+) t1;
+
+select a/0 from t_1000_800;
+select a/0 from t_1000_995;
+select a/0 from t_1000_1000;
+
+select a%0 from t_1000_800;
+select a%0 from t_1000_995;
+select a%0 from t_1000_1000;
+
+select (a-a)/(a-a) from t_1000_800;
+select (a-a)/(a-a) from t_1000_995;
+select (a-a)/(a-a) from t_1000_1000;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_800 t1 join t_1000_800 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_995 t1 join t_1000_995 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_1000 t1 join t_1000_1000 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_800 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_995 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_1000 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_ddl.sql b/src/test/regress/sql/numeric_op_ddl.sql
new file mode 100644
index 00000000000..5d213c75acd
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_ddl.sql
@@ -0,0 +1,173 @@
+DROP TABLE IF EXISTS t_9_2;
+DROP TABLE IF EXISTS t_18_2;
+DROP TABLE IF EXISTS t_38_2;
+DROP TABLE IF EXISTS t_80_16;
+DROP TABLE IF EXISTS t_175_35;
+DROP TABLE IF EXISTS t_400_80;
+DROP TABLE IF EXISTS t_1000_200;
+DROP TABLE IF EXISTS t_1000_800;
+DROP TABLE IF EXISTS t_1000_995;
+
+DROP TABLE IF EXISTS t_base;
+
+CREATE TABLE t_9_2 (id int, id2 int, a NUMERIC(9, 2), b NUMERIC(11, 4));
+CREATE TABLE t_18_2 (id int, id2 int, a NUMERIC(18, 2), b NUMERIC(20, 4));
+CREATE TABLE t_38_2 (id int, id2 int, a NUMERIC(38, 2), b NUMERIC(40, 4));
+CREATE TABLE t_80_16 (id int, id2 int, a NUMERIC(80, 16), b NUMERIC(90, 18));
+CREATE TABLE t_175_35 (id int, id2 int, a NUMERIC(175, 35), b NUMERIC(190, 40));
+CREATE TABLE t_400_80 (id int, id2 int, a NUMERIC(400, 80), b NUMERIC(420, 100));
+CREATE TABLE t_1000_200 (id int, id2 int, a NUMERIC(1000, 200), b NUMERIC(1000, 220));
+CREATE TABLE t_1000_800 (id int, id2 int, a NUMERIC(1000, 800), b NUMERIC(1000, 900));
+CREATE TABLE t_1000_995 (id int, id2 int, a NUMERIC(1000, 995), b NUMERIC(1000, 1000));
+
+CREATE TABLE t_base (id int, id2 int, astr text, bstr text);
+
+INSERT INTO t_base
+    SELECT  g,
+            g % 20,
+            string_agg(
+                lpad(
+                    (((g * 37 + k)::bigint * 1103515245 + 12345) % 1000000007)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS astr,
+            string_agg(
+                lpad(
+                    (((g * 131 + k)::bigint * 1284584123 + 89247) % 998244353)::text,
+                    9, '0'
+                    ),
+                ''
+                ORDER BY k
+            ) AS bstr
+    FROM generate_series(1, 2000) g,
+        generate_series(1, 120) k
+    GROUP BY g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 7) || '.' || substr(astr, 8, 2))::numeric(9, 2) AS a,
+        (substr(bstr, 1, 7) || '.' || substr(bstr, 8, 4))::numeric(11, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_9_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 16) || '.' || substr(astr, 17, 2))::numeric(18, 2) AS a,
+        (substr(bstr, 1, 16) || '.' || substr(bstr, 17, 4))::numeric(20, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_18_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 36) || '.' || substr(astr, 37, 2))::numeric(38, 2) AS a,
+        (substr(bstr, 1, 36) || '.' || substr(bstr, 37, 4))::numeric(40, 4) AS b
+    FROM t_base
+)
+INSERT INTO t_38_2 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+    
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 64) || '.' || substr(astr, 65, 16))::numeric(80, 16) AS a,
+        (substr(bstr, 1, 72) || '.' || substr(bstr, 73, 18))::numeric(90, 18) AS b
+    FROM t_base
+)
+INSERT INTO t_80_16 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 140) || '.' || substr(astr, 141, 35))::numeric(175, 35) AS a,
+        (substr(bstr, 1, 150) || '.' || substr(bstr, 151, 40))::numeric(190, 40) AS b
+    FROM t_base
+)
+INSERT INTO t_175_35 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 320) || '.' || substr(astr, 321, 80))::numeric(400, 80) AS a,
+        (substr(bstr, 1, 320) || '.' || substr(bstr, 321, 100))::numeric(420, 100) AS b
+    FROM t_base
+)
+INSERT INTO t_400_80 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 800) || '.' || substr(astr, 801, 200))::numeric(1000, 200) AS a,
+        (substr(bstr, 1, 780) || '.' || substr(bstr, 781, 220))::numeric(1000, 220) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_200 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 200) || '.' || substr(astr, 201, 800))::numeric(1000, 800) AS a,
+        (substr(bstr, 1, 100) || '.' || substr(bstr, 101, 900))::numeric(1000, 900) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_800 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+WITH t AS (
+    SELECT id, id2,
+        (substr(astr, 1, 5) || '.' || substr(astr, 6, 995))::numeric(1000, 995) AS a,
+        ('0.' || substr(bstr, 1, 1000))::numeric(1000, 1000) AS b
+    FROM t_base
+)
+INSERT INTO t_1000_995 SELECT id, id2, a, b FROM t
+    UNION ALL SELECT id, id2, -a, -b FROM t
+    UNION ALL SELECT g AS id, g % 20 AS id2, 0 AS a, 0 AS b from generate_series(1, 100) AS g;
+
+CREATE INDEX idx_t_9_2 ON t_9_2 USING btree(a);
+CREATE INDEX idx_t_18_2 ON t_18_2 USING btree(a);
+CREATE INDEX idx_t_38_2 ON t_38_2 USING btree(a);
+CREATE INDEX idx_t_80_16 ON t_80_16 USING btree(a);
+CREATE INDEX idx_t_175_35 ON t_175_35 USING btree(a);
+CREATE INDEX idx_t_400_80 ON t_400_80 USING btree(a);
+CREATE INDEX idx_t_1000_200 ON t_1000_200 USING btree(a);
+CREATE INDEX idx_t_1000_800 ON t_1000_800 USING btree(a);
+CREATE INDEX idx_t_1000_995 ON t_1000_995 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_9_2;
+VACUUM FREEZE ANALYZE t_18_2;
+VACUUM FREEZE ANALYZE t_38_2;
+VACUUM FREEZE ANALYZE t_80_16;
+VACUUM FREEZE ANALYZE t_175_35;
+VACUUM FREEZE ANALYZE t_400_80;
+VACUUM FREEZE ANALYZE t_1000_200;
+VACUUM FREEZE ANALYZE t_1000_800;
+VACUUM FREEZE ANALYZE t_1000_995;
+
+DROP TABLE IF EXISTS t_1000_1000;
+CREATE TABLE t_1000_1000 AS
+SELECT
+gs as id,
+gs % 20 as id2,
+(
+    REPEAT('9', (gs % 200)) || '.' || REPEAT('9', 800)
+)::NUMERIC(1000, 800) AS a,
+(
+    REPEAT('0', (gs % 200)) || '.' || REPEAT('0', 800)
+)::NUMERIC(1000, 800) AS b
+FROM GENERATE_SERIES(1, 1000) AS gs;
+
+CREATE INDEX idx_t_1000_1000 ON t_1000_1000 USING btree(a);
+
+VACUUM FREEZE ANALYZE t_1000_1000;
\ No newline at end of file
diff --git a/src/test/regress/sql/numeric_op_medium.sql b/src/test/regress/sql/numeric_op_medium.sql
new file mode 100644
index 00000000000..079dd414d19
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_medium.sql
@@ -0,0 +1,269 @@
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_175_35 where a > 1000 order by a;
+explain (costs off) select a from t_175_35 where a > 1000 order by a;
+explain (costs off) select * from t_400_80 where a > 1000 order by a;
+explain (costs off) select a from t_400_80 where a > 1000 order by a;
+explain (costs off) select * from t_1000_200 where a > 1000 order by a;
+explain (costs off) select a from t_1000_200 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_175_35 set a = a + 1;
+update t_400_80 set a = a + 1;
+update t_1000_200 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_175_35 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_400_80 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_1000_200 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_175_35;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_400_80;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_1000_200;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_175_35
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_400_80
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_1000_200
+) t1;
+
+select a/0 from t_175_35;
+select a/0 from t_400_80;
+select a/0 from t_1000_200;
+
+select a%0 from t_175_35;
+select a%0 from t_400_80;
+select a%0 from t_1000_200;
+
+select (a-a)/(a-a) from t_175_35;
+select (a-a)/(a-a) from t_400_80;
+select (a-a)/(a-a) from t_1000_200;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_175_35 t1 join t_175_35 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_400_80 t1 join t_400_80 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_1000_200 t1 join t_1000_200 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_175_35 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_400_80 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_1000_200 where a > 100) t1;
diff --git a/src/test/regress/sql/numeric_op_small.sql b/src/test/regress/sql/numeric_op_small.sql
new file mode 100644
index 00000000000..90d50d24d9c
--- /dev/null
+++ b/src/test/regress/sql/numeric_op_small.sql
@@ -0,0 +1,335 @@
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_seqscan = off;
+set enable_bitmapscan = off;
+explain (costs off) select * from t_9_2 where a > 1000 order by a;
+explain (costs off) select a from t_9_2 where a > 1000 order by a;
+explain (costs off) select * from t_18_2 where a > 1000 order by a;
+explain (costs off) select a from t_18_2 where a > 1000 order by a;
+explain (costs off) select * from t_38_2 where a > 1000 order by a;
+explain (costs off) select a from t_38_2 where a > 1000 order by a;
+explain (costs off) select * from t_80_16 where a > 1000 order by a;
+explain (costs off) select a from t_80_16 where a > 1000 order by a;
+reset enable_seqscan;
+reset enable_bitmapscan;
+update t_9_2 set a = a + 1;
+update t_18_2 set a = a + 1;
+update t_38_2 set a = a + 1;
+update t_80_16 set a = a + 1;
+
+select count(distinct a), sum(hashtext(a::text)) from t_9_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_18_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_38_2 where a > b;
+select count(distinct a), sum(hashtext(a::text)) from t_80_16 where a > b;
+
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_9_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_18_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_38_2;
+select sum(hashtext((a+b)::text)),
+    sum(hashtext((a-b)::text)),
+    sum(hashtext((a*b)::text)),
+    sum(hashtext((a/nullif(b, 0))::text)),
+    sum(hashtext((a%nullif(b, 0))::text)),
+    sum(hashtext((a*b+b/nullif(a, 0))::text)),
+    sum(hashtext((a-b/nullif((b-a), 0))::text)),
+    sum(hashtext((a > b)::text)),
+    sum(hashtext((a < b)::text)),
+    sum(hashtext((a = b)::text)),
+    sum(hashtext(abs(a)::text)),
+    sum(hashtext(round(a)::text)),
+    sum(hashtext(trunc(a)::text))
+from t_80_16;
+
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_9_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_18_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_38_2
+) t1;
+select sum(hashtext((rk)::text)), sum(hashtext((rn)::text)), sum(hashtext((s)::text)) from (
+    select rank() over(partition by id2 order by a+b, a, b, id) as rk,
+        row_number() over(partition by id2 order by b, a, id) as rn,
+        sum(a) over(
+            partition by id2
+            order by a, b, id
+            rows between unbounded preceding and current row
+        ) as s
+    from t_80_16
+) t1;
+
+select a/0 from t_9_2;
+select a/0 from t_18_2;
+select a/0 from t_38_2;
+select a/0 from t_80_16;
+
+select a%0 from t_9_2;
+select a%0 from t_18_2;
+select a%0 from t_38_2;
+select a%0 from t_80_16;
+
+select (a-a)/(a-a) from t_9_2;
+select (a-a)/(a-a) from t_18_2;
+select (a-a)/(a-a) from t_38_2;
+select (a-a)/(a-a) from t_80_16;
+
+set enable_hashjoin = on;
+set enable_nestloop = off;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+set enable_hashjoin = off;
+set enable_nestloop = off;
+set enable_mergejoin = on;
+
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_9_2 t1 join t_9_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_18_2 t1 join t_18_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_38_2 t1 join t_38_2 t2 on t1.a = t2.a;
+select md5(sum(t1.a + t2.b)::text), md5(avg(t1.a * t2.b)::text), md5(stddev(t1.a - t2.b)::text) from t_80_16 t1 join t_80_16 t2 on t1.a = t2.a;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+reset enable_mergejoin;
+
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_9_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_18_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_38_2 where a > 100) t1;
+select sum(hashtext((a*rn)::text)) from (select a, row_number() over(order by a, id) as rn from t_80_16 where a > 100) t1;
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: [PATCH v4] Make NumericVar storage semantics explicit
@ 2026-05-24 07:05  =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  parent: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: =?utf-8?B?Q2hlbmh1aU1v?= @ 2026-05-24 07:05 UTC (permalink / raw)
  To: =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>; pgsql-hackers

Hi all,

This patch has been refreshed since the original posting, and the current
version passes CI and includes fixes for the issues found in the earlier
revisions.

I'd appreciate any review when time permits.

Thanks.
Regards,
Chenhui Mo

^ permalink  raw  reply  [nested|flat] 8+ messages in thread


end of thread, other threads:[~2026-05-24 07:05 UTC | newest]

Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-09-10 14:26 [PATCH 1/4] Fix GiST and SP-GiST ordering by distance for NULLs and NaNs Nikita Glukhov <[email protected]>
2019-09-10 14:26 [PATCH 1/4] Fix GiST and SP-GiST ordering by distance for NULLs Nikita Glukhov <[email protected]>
2019-09-10 14:26 [PATCH 1/5] Fix GiST and SP-GiST ordering by distance for NULLs Nikita Glukhov <[email protected]>
2026-05-10 14:12 [PATCH] Make NumericVar storage semantics explicit =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
2026-05-11 10:55 ` Re: [PATCH v2] Make NumericVar storage semantics explicit =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
2026-05-16 04:37   ` Re: [PATCH v3] Make NumericVar storage semantics explicit =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
2026-05-16 14:32     ` Re: [PATCH v4] Make NumericVar storage semantics explicit =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>
2026-05-24 07:05       ` Re: [PATCH v4] Make NumericVar storage semantics explicit =?utf-8?B?Q2hlbmh1aU1v?= <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox