From e66d714386cd3040b328d6a894802d01dd1d395d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 2 Oct 2009 21:14:04 +0000
Subject: [PATCH] Make sure that GIN fast-insert and regular code paths enforce
 the same tuple size limit.  Improve the error message for
 index-tuple-too-large so that it includes the actual size, the limit, and the
 index name. Sync with the btree occurrences of the same error.

Back-patch to 8.4 because it appears that the out-of-sync problem
is occurring in the field.

Teodor and Tom
---
 src/backend/access/gin/ginentrypage.c | 52 ++++++++++++++++++++++-----
 src/backend/access/gin/ginfast.c      | 15 +++-----
 src/backend/access/gin/gininsert.c    | 16 ++++-----
 src/backend/access/gin/ginvacuum.c    |  5 +--
 src/backend/access/nbtree/nbtinsert.c |  7 ++--
 src/backend/access/nbtree/nbtsort.c   |  7 ++--
 src/include/access/gin.h              | 11 +++---
 7 files changed, 72 insertions(+), 41 deletions(-)

diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c
index c4659cde1f0..87cc407233c 100644
--- a/src/backend/access/gin/ginentrypage.c
+++ b/src/backend/access/gin/ginentrypage.c
@@ -8,20 +8,22 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.21 2009/06/11 14:48:53 momjian Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.22 2009/10/02 21:14:04 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
 #include "access/gin.h"
-#include "access/tuptoaster.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
 /*
  * Form a tuple for entry tree.
  *
+ * If the tuple would be too big to be stored, function throws a suitable
+ * error if errorTooBig is TRUE, or returns NULL if errorTooBig is FALSE.
+ *
  * On leaf pages, Index tuple has non-traditional layout. Tuple may contain
  * posting list or root blocknumber of posting tree.
  * Macros: GinIsPostingTree(itup) / GinSetPostingTree(itup, blkno)
@@ -49,10 +51,13 @@
  * and value.
  */
 IndexTuple
-GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, ItemPointerData *ipd, uint32 nipd)
+GinFormTuple(Relation index, GinState *ginstate,
+			 OffsetNumber attnum, Datum key,
+			 ItemPointerData *ipd, uint32 nipd, bool errorTooBig)
 {
 	bool		isnull[2] = {FALSE, FALSE};
 	IndexTuple	itup;
+	uint32		newsize;
 
 	if (ginstate->oneCol)
 		itup = index_form_tuple(ginstate->origTupdesc, &key, isnull);
@@ -69,13 +74,19 @@ GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, ItemPointerData
 
 	if (nipd > 0)
 	{
-		uint32		newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData) * nipd);
-
-		if (newsize >= INDEX_SIZE_MASK)
-			return NULL;
-
-		if (newsize > TOAST_INDEX_TARGET && nipd > 1)
+		newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData) * nipd);
+		if (newsize > Min(INDEX_SIZE_MASK, GinMaxItemSize))
+		{
+			if (errorTooBig)
+				ereport(ERROR,
+						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+						 errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
+								(unsigned long) newsize,
+								(unsigned long) Min(INDEX_SIZE_MASK,
+													GinMaxItemSize),
+								RelationGetRelationName(index))));
 			return NULL;
+		}
 
 		itup = repalloc(itup, newsize);
 
@@ -89,6 +100,29 @@ GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, ItemPointerData
 	}
 	else
 	{
+		/*
+		 * Gin tuple without any ItemPointers should be large enough to keep
+		 * one ItemPointer, to prevent inconsistency between
+		 * ginHeapTupleFastCollect and ginEntryInsert called by
+		 * ginHeapTupleInsert.  ginHeapTupleFastCollect forms tuple without
+		 * extra pointer to heap, but ginEntryInsert (called for pending list
+		 * cleanup during vacuum) will form the same tuple with one
+		 * ItemPointer.
+		 */
+		newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData));
+		if (newsize > Min(INDEX_SIZE_MASK, GinMaxItemSize))
+		{
+			if (errorTooBig)
+				ereport(ERROR,
+						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+						 errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
+								(unsigned long) newsize,
+								(unsigned long) Min(INDEX_SIZE_MASK,
+													GinMaxItemSize),
+								RelationGetRelationName(index))));
+			return NULL;
+		}
+
 		GinSetNPosting(itup, 0);
 	}
 	return itup;
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 9d2351af529..a5d02e74a13 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginfast.c,v 1.4 2009/09/15 20:31:30 tgl Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginfast.c,v 1.5 2009/10/02 21:14:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,7 +20,6 @@
 
 #include "access/genam.h"
 #include "access/gin.h"
-#include "access/tuptoaster.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -465,16 +464,10 @@ ginHeapTupleFastCollect(Relation index, GinState *ginstate,
 	 */
 	for (i = 0; i < nentries; i++)
 	{
-		int32		tupsize;
-
-		collector->tuples[collector->ntuples + i] = GinFormTuple(ginstate, attnum, entries[i], NULL, 0);
+		collector->tuples[collector->ntuples + i] =
+			GinFormTuple(index, ginstate, attnum, entries[i], NULL, 0, true);
 		collector->tuples[collector->ntuples + i]->t_tid = *item;
-		tupsize = IndexTupleSize(collector->tuples[collector->ntuples + i]);
-
-		if (tupsize > TOAST_INDEX_TARGET || tupsize >= GinMaxItemSize)
-			elog(ERROR, "huge tuple");
-
-		collector->sumsize += tupsize;
+		collector->sumsize += IndexTupleSize(collector->tuples[collector->ntuples + i]);
 	}
 
 	collector->ntuples += nentries;
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index d175a5a99e6..17f88f62d16 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.23 2009/07/29 20:56:17 tgl Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.24 2009/10/02 21:14:04 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -102,8 +102,9 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
 {
 	Datum		key = gin_index_getattr(ginstate, old);
 	OffsetNumber attnum = gintuple_get_attrnum(ginstate, old);
-	IndexTuple	res = GinFormTuple(ginstate, attnum, key,
-								   NULL, nitem + GinGetNPosting(old));
+	IndexTuple	res = GinFormTuple(index, ginstate, attnum, key,
+								   NULL, nitem + GinGetNPosting(old),
+								   false);
 
 	if (res)
 	{
@@ -122,7 +123,7 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
 		GinPostingTreeScan *gdi;
 
 		/* posting list becomes big, so we need to make posting's tree */
-		res = GinFormTuple(ginstate, attnum, key, NULL, 0);
+		res = GinFormTuple(index, ginstate, attnum, key, NULL, 0, true);
 		postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old));
 		GinSetPostingTree(res, postingRoot);
 
@@ -185,13 +186,12 @@ ginEntryInsert(Relation index, GinState *ginstate,
 	}
 	else
 	{
-		/* We suppose, that tuple can store at list one itempointer */
-		itup = GinFormTuple(ginstate, attnum, value, items, 1);
-		if (itup == NULL || IndexTupleSize(itup) >= GinMaxItemSize)
-			elog(ERROR, "huge tuple");
+		/* We suppose that tuple can store at least one itempointer */
+		itup = GinFormTuple(index, ginstate, attnum, value, items, 1, true);
 
 		if (nitem > 1)
 		{
+			/* Add the rest, making a posting tree if necessary */
 			IndexTuple	previtup = itup;
 
 			itup = addItemPointersToTuple(index, ginstate, stack, previtup, items + 1, nitem - 1, isBuild);
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 7f2ff9896f0..93090efc902 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.30 2009/06/11 14:48:53 momjian Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.31 2009/10/02 21:14:04 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -564,7 +564,8 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3
 
 				value = gin_index_getattr(&gvs->ginstate, itup);
 				attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
-				itup = GinFormTuple(&gvs->ginstate, attnum, value, GinGetPosting(itup), newN);
+				itup = GinFormTuple(gvs->index, &gvs->ginstate, attnum, value,
+									GinGetPosting(itup), newN, true);
 				PageIndexTupleDelete(tmppage, i);
 
 				if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index dd3736ad8a9..a1dadfb6923 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.173 2009/08/01 20:59:17 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.174 2009/10/02 21:14:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -518,9 +518,10 @@ _bt_findinsertloc(Relation rel,
 	if (itemsz > BTMaxItemSize(page))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				 errmsg("index row size %lu exceeds btree maximum, %lu",
+				 errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
 						(unsigned long) itemsz,
-						(unsigned long) BTMaxItemSize(page)),
+						(unsigned long) BTMaxItemSize(page),
+						RelationGetRelationName(rel)),
 		errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
 				"Consider a function index of an MD5 hash of the value, "
 				"or use full text indexing.")));
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index ff5a54ec15a..c8da41076a1 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -59,7 +59,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsort.c,v 1.119 2009/01/01 17:23:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsort.c,v 1.120 2009/10/02 21:14:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -480,9 +480,10 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
 	if (itupsz > BTMaxItemSize(npage))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				 errmsg("index row size %lu exceeds btree maximum, %lu",
+				 errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
 						(unsigned long) itupsz,
-						(unsigned long) BTMaxItemSize(npage)),
+						(unsigned long) BTMaxItemSize(npage),
+						RelationGetRelationName(wstate->index)),
 		errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
 				"Consider a function index of an MD5 hash of the value, "
 				"or use full text indexing.")));
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 3f814eed73a..a0d6a0d8f27 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -4,7 +4,7 @@
  *
  *	Copyright (c) 2006-2009, PostgreSQL Global Development Group
  *
- *	$PostgreSQL: pgsql/src/include/access/gin.h,v 1.34 2009/06/11 14:49:08 momjian Exp $
+ *	$PostgreSQL: pgsql/src/include/access/gin.h,v 1.35 2009/10/02 21:14:04 tgl Exp $
  *--------------------------------------------------------------------------
  */
 #ifndef GIN_H
@@ -165,8 +165,8 @@ typedef struct
 #define GinGetPosting(itup)			( (ItemPointer)(( ((char*)(itup)) + SHORTALIGN(GinGetOrigSizePosting(itup)) )) )
 
 #define GinMaxItemSize \
-	((BLCKSZ - SizeOfPageHeaderData - \
-		MAXALIGN(sizeof(GinPageOpaqueData))) / 3 - sizeof(ItemIdData))
+	MAXALIGN_DOWN(((BLCKSZ - SizeOfPageHeaderData - \
+		MAXALIGN(sizeof(GinPageOpaqueData))) / 3 - sizeof(ItemIdData)))
 
 
 /*
@@ -434,8 +434,9 @@ extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack);
 extern void findParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno);
 
 /* ginentrypage.c */
-extern IndexTuple GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key,
-			 ItemPointerData *ipd, uint32 nipd);
+extern IndexTuple GinFormTuple(Relation index, GinState *ginstate,
+			 OffsetNumber attnum, Datum key,
+			 ItemPointerData *ipd, uint32 nipd, bool errorTooBig);
 extern void GinShortenTuple(IndexTuple itup, uint32 nipd);
 extern void prepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum,
 				 Datum value, GinState *ginstate);
-- 
GitLab