From 0b9be8e526335686d2a4eeba5a62313b5018a5c4 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Mon, 25 Jun 2018 16:31:47 +1200
Subject: [PATCH v1] Improve best case speed of tuple conversion map generation

Previously convert_tuples_by_name_map naively performed a search of all
inner loop columns for each loop done in the outer loop.  When partitioned
tables had many columns this could result in slow generation of the tuple
conversion maps.  For INSERT and UPDATE statements that touched few rows,
this could mean a very large overhead indeed.

We can do a bit better with this loop.  It's quite likely that the columns
in partitioned tables and their partitions are in the same order, so it
makes sense to start searching in the inner loop at the same position that
we're on in the outer loop.  This makes the best case search O(N) (N being
the number of columns).  The worst case is still O(N^2), but it seems
unlikely that would happen, although we must take some protection against
starting the search one column too late due to a dropped column.  Without
this protection the worst case could be easy to hit.
---
 src/backend/access/common/tupconvert.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 2d0d2f4b32..578bf7f2ba 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -297,6 +297,7 @@ convert_tuples_by_name_map(TupleDesc indesc,
 	AttrNumber *attrMap;
 	int			n;
 	int			i;
+	int			outnondropped = 0;
 
 	n = outdesc->natts;
 	attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
@@ -313,9 +314,22 @@ convert_tuples_by_name_map(TupleDesc indesc,
 		attname = NameStr(outatt->attname);
 		atttypid = outatt->atttypid;
 		atttypmod = outatt->atttypmod;
+
+		/*
+		 * Now search for an attribute with the same name in the indesc. It
+		 * seems likely that a partitioned table will have the attributes in
+		 * the same order as the partition, so we optimistically start our
+		 * search at the same attnum (minus the so-far counted number of
+		 * dropped columns in outdesc).  If a column were dropped in outdesc
+		 * and not indesc then if we didn't account for the dropped column, it
+		 * could mean we end up starting our search one column after the one
+		 * we want to find, resulting in only finding our target on the final
+		 * column we check.
+		 */
 		for (j = 0; j < indesc->natts; j++)
 		{
-			Form_pg_attribute inatt = TupleDescAttr(indesc, j);
+			int k = (outnondropped + j) % indesc->natts;
+			Form_pg_attribute inatt = TupleDescAttr(indesc, k);
 
 			if (inatt->attisdropped)
 				continue;
@@ -330,7 +344,7 @@ convert_tuples_by_name_map(TupleDesc indesc,
 									   attname,
 									   format_type_be(outdesc->tdtypeid),
 									   format_type_be(indesc->tdtypeid))));
-				attrMap[i] = (AttrNumber) (j + 1);
+				attrMap[i] = inatt->attnum;
 				break;
 			}
 		}
@@ -342,6 +356,9 @@ convert_tuples_by_name_map(TupleDesc indesc,
 							   attname,
 							   format_type_be(outdesc->tdtypeid),
 							   format_type_be(indesc->tdtypeid))));
+
+		/* keep track of number of non-dropped outdesc columns */
+		outnondropped++;
 	}
 
 	return attrMap;
-- 
2.16.2.windows.1

