From 7ff02add837eb34354ca7197e521da3260aef9c1 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 16 Nov 2010 11:02:11 +0200
Subject: [PATCH] The GiST scan algorithm uses LSNs to detect concurrent pages
 splits, but temporary indexes are not WAL-logged. We used a constant LSN for
 temporary indexes, on the assumption that we don't need to worry about
 concurrent page splits in temporary indexes because they're only visible to
 the current session. But that assumption is wrong, it's possible to insert
 rows and split pages in the same session, while a scan is in progress. For
 example, by opening a cursor and fetching some rows, and INSERTing new rows
 before fetching some more.

Fix by generating fake increasing LSNs, used in place of real LSNs in
temporary GiST indexes.
---
 src/backend/access/gist/gist.c       | 10 ++++------
 src/backend/access/gist/gistutil.c   | 21 +++++++++++++++++++++
 src/backend/access/gist/gistvacuum.c |  8 ++++----
 src/include/access/gist_private.h    |  3 ++-
 4 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 2742969c6a..405934caad 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -22,8 +22,6 @@
 #include "storage/indexfsm.h"
 #include "utils/memutils.h"
 
-const XLogRecPtr XLogRecPtrForTemp = {1, 1};
-
 /* Working state for gistbuild and its callback */
 typedef struct
 {
@@ -132,7 +130,7 @@ gistbuild(PG_FUNCTION_ARGS)
 		PageSetTLI(page, ThisTimeLineID);
 	}
 	else
-		PageSetLSN(page, XLogRecPtrForTemp);
+		PageSetLSN(page, GetXLogRecPtrForTemp());
 
 	UnlockReleaseBuffer(buffer);
 
@@ -423,7 +421,7 @@ gistplacetopage(GISTInsertState *state, GISTSTATE *giststate)
 		{
 			for (ptr = dist; ptr; ptr = ptr->next)
 			{
-				PageSetLSN(ptr->page, XLogRecPtrForTemp);
+				PageSetLSN(ptr->page, GetXLogRecPtrForTemp());
 			}
 		}
 
@@ -491,7 +489,7 @@ gistplacetopage(GISTInsertState *state, GISTSTATE *giststate)
 			PageSetTLI(state->stack->page, ThisTimeLineID);
 		}
 		else
-			PageSetLSN(state->stack->page, XLogRecPtrForTemp);
+			PageSetLSN(state->stack->page, GetXLogRecPtrForTemp());
 
 		if (state->stack->blkno == GIST_ROOT_BLKNO)
 			state->needInsertComplete = false;
@@ -1027,7 +1025,7 @@ gistnewroot(Relation r, Buffer buffer, IndexTuple *itup, int len, ItemPointer ke
 		PageSetTLI(page, ThisTimeLineID);
 	}
 	else
-		PageSetLSN(page, XLogRecPtrForTemp);
+		PageSetLSN(page, GetXLogRecPtrForTemp());
 
 	END_CRIT_SECTION();
 }
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 78eb378725..a2e8fe0cfb 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -677,3 +677,24 @@ gistoptions(PG_FUNCTION_ARGS)
 		PG_RETURN_BYTEA_P(result);
 	PG_RETURN_NULL();
 }
+
+/*
+ * Temporary GiST indexes are not WAL-logged, but we need LSNs to detect
+ * concurrent page splits anyway. GetXLogRecPtrForTemp() provides a fake
+ * sequence of LSNs for that purpose. Each call generates an LSN that is
+ * greater than any previous value returned by this function in the same
+ * session.
+ */
+XLogRecPtr
+GetXLogRecPtrForTemp(void)
+{
+	static XLogRecPtr counter = {0, 1};
+
+	counter.xrecoff++;
+	if (counter.xrecoff == 0)
+	{
+		counter.xlogid++;
+		counter.xrecoff++;
+	}
+	return counter;
+}
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 975f9d8c56..0433b47ae1 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -138,7 +138,7 @@ gistDeleteSubtree(GistVacuum *gv, BlockNumber blkno)
 		PageSetTLI(page, ThisTimeLineID);
 	}
 	else
-		PageSetLSN(page, XLogRecPtrForTemp);
+		PageSetLSN(page, GetXLogRecPtrForTemp());
 
 	END_CRIT_SECTION();
 
@@ -245,7 +245,7 @@ vacuumSplitPage(GistVacuum *gv, Page tempPage, Buffer buffer, IndexTuple *addon,
 	else
 	{
 		for (ptr = dist; ptr; ptr = ptr->next)
-			PageSetLSN(BufferGetPage(ptr->buffer), XLogRecPtrForTemp);
+			PageSetLSN(BufferGetPage(ptr->buffer), GetXLogRecPtrForTemp());
 	}
 
 	for (ptr = dist; ptr; ptr = ptr->next)
@@ -460,7 +460,7 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
 				pfree(rdata);
 			}
 			else
-				PageSetLSN(page, XLogRecPtrForTemp);
+				PageSetLSN(page, GetXLogRecPtrForTemp());
 		}
 
 		END_CRIT_SECTION();
@@ -774,7 +774,7 @@ gistbulkdelete(PG_FUNCTION_ARGS)
 					pfree(rdata);
 				}
 				else
-					PageSetLSN(page, XLogRecPtrForTemp);
+					PageSetLSN(page, GetXLogRecPtrForTemp());
 
 				END_CRIT_SECTION();
 			}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 2ce46542af..e375326491 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -87,7 +87,6 @@ typedef struct GISTScanOpaqueData
 typedef GISTScanOpaqueData *GISTScanOpaque;
 
 /* XLog stuff */
-extern const XLogRecPtr XLogRecPtrForTemp;
 
 #define XLOG_GIST_PAGE_UPDATE		0x00
 #define XLOG_GIST_NEW_ROOT			0x20
@@ -326,6 +325,8 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno,
 				 GISTENTRY *entry2, bool isnull2,
 				 Datum *dst, bool *dstisnull);
 
+extern XLogRecPtr GetXLogRecPtrForTemp(void);
+
 /* gistvacuum.c */
 extern Datum gistbulkdelete(PG_FUNCTION_ARGS);
 extern Datum gistvacuumcleanup(PG_FUNCTION_ARGS);