0002-The-remainder-of-Marko-s-patch.patch

application/octet-stream

Filename: 0002-The-remainder-of-Marko-s-patch.patch
Type: application/octet-stream
Part: 1
Message: Re: Review: Fix snapshot taking inconsistencies

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0002
Subject: The remainder of Marko's patch
File+
src/backend/executor/functions.c 177 84
src/backend/executor/spi.c 17 1
src/backend/tcop/pquery.c 29 7
From 297de9f38c1b4b1dbdb7812081b6c8058aff6b33 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 20 Oct 2010 16:27:36 -0300
Subject: [PATCH 2/2] The remainder of Marko's patch

---
 src/backend/executor/functions.c |  261 ++++++++++++++++++++++++++------------
 src/backend/executor/spi.c       |   18 +++-
 src/backend/tcop/pquery.c        |   36 ++++-
 3 files changed, 223 insertions(+), 92 deletions(-)

diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 46433d0..6310fa1 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -90,18 +90,18 @@ typedef struct
 	ParamListInfo paramLI;		/* Param list representing current args */
 
 	Tuplestorestate *tstore;	/* where we accumulate result tuples */
+	Snapshot	snapshot;
 
 	JunkFilter *junkFilter;		/* will be NULL if function returns VOID */
 
-	/* head of linked list of execution_state records */
-	execution_state *func_state;
+	List *func_state;
 } SQLFunctionCache;
 
 typedef SQLFunctionCache *SQLFunctionCachePtr;
 
 
 /* non-export function prototypes */
-static execution_state *init_execution_state(List *queryTree_list,
+static List *init_execution_state(List *queryTree_list,
 					 SQLFunctionCachePtr fcache,
 					 bool lazyEvalOK);
 static void init_sql_fcache(FmgrInfo *finfo, bool lazyEvalOK);
@@ -123,61 +123,78 @@ static void sqlfunction_destroy(DestReceiver *self);
 
 
 /* Set up the list of per-query execution_state records for a SQL function */
-static execution_state *
+static List *
 init_execution_state(List *queryTree_list,
 					 SQLFunctionCachePtr fcache,
 					 bool lazyEvalOK)
 {
-	execution_state *firstes = NULL;
-	execution_state *preves = NULL;
+	execution_state *firstes;
+	execution_state *preves;
 	execution_state *lasttages = NULL;
-	ListCell   *qtl_item;
+	List	   *eslist;
+	ListCell   *lc1;
+	ListCell   *lc2;
+	List	   *qtlist;
+	Query	   *queryTree;
+
+
+	eslist = NIL;
 
-	foreach(qtl_item, queryTree_list)
+	foreach(lc1, queryTree_list)
 	{
-		Query	   *queryTree = (Query *) lfirst(qtl_item);
-		Node	   *stmt;
-		execution_state *newes;
+		qtlist = (List *) lfirst(lc1);
+		firstes = NULL;
+		preves = NULL;
 
-		Assert(IsA(queryTree, Query));
+		foreach(lc2, qtlist)
+		{
+			Node	   *stmt;
+			execution_state *newes;
 
-		if (queryTree->commandType == CMD_UTILITY)
-			stmt = queryTree->utilityStmt;
-		else
-			stmt = (Node *) pg_plan_query(queryTree, 0, NULL);
+			queryTree = (Query *) lfirst(lc2);
 
-		/* Precheck all commands for validity in a function */
-		if (IsA(stmt, TransactionStmt))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			/* translator: %s is a SQL statement name */
-					 errmsg("%s is not allowed in a SQL function",
-							CreateCommandTag(stmt))));
+			Assert(IsA(queryTree, Query));
 
-		if (fcache->readonly_func && !CommandIsReadOnly(stmt))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			/* translator: %s is a SQL statement name */
-					 errmsg("%s is not allowed in a non-volatile function",
-							CreateCommandTag(stmt))));
+			if (queryTree->commandType == CMD_UTILITY)
+				stmt = queryTree->utilityStmt;
+			else
+				stmt = (Node *) pg_plan_query(queryTree, 0, NULL);
 
-		newes = (execution_state *) palloc(sizeof(execution_state));
-		if (preves)
-			preves->next = newes;
-		else
-			firstes = newes;
+			/* Precheck all commands for validity in a function */
+			if (IsA(stmt, TransactionStmt))
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				/* translator: %s is a SQL statement name */
+						 errmsg("%s is not allowed in a SQL function",
+								CreateCommandTag(stmt))));
 
-		newes->next = NULL;
-		newes->status = F_EXEC_START;
-		newes->setsResult = false;		/* might change below */
-		newes->lazyEval = false;	/* might change below */
-		newes->stmt = stmt;
-		newes->qd = NULL;
+			if (fcache->readonly_func && !CommandIsReadOnly(stmt))
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				/* translator: %s is a SQL statement name */
+						 errmsg("%s is not allowed in a non-volatile function",
+								CreateCommandTag(stmt))));
+
+			newes = (execution_state *) palloc(sizeof(execution_state));
+			if (preves)
+				preves->next = newes;
+			else
+				firstes = newes;
 
-		if (queryTree->canSetTag)
-			lasttages = newes;
+			newes->next = NULL;
+			newes->status = F_EXEC_START;
+			newes->setsResult = false;		/* might change below */
+			newes->lazyEval = false;	/* might change below */
+			newes->stmt = stmt;
+			newes->qd = NULL;
 
-		preves = newes;
+			if (queryTree->canSetTag)
+				lasttages = newes;
+
+			preves = newes;
+		}
+
+		eslist = lappend(eslist, firstes);
 	}
 
 	/*
@@ -210,7 +227,7 @@ init_execution_state(List *queryTree_list,
 		}
 	}
 
-	return firstes;
+	return eslist;
 }
 
 /* Initialize the SQLFunctionCache for a SQL function */
@@ -388,24 +405,11 @@ init_sql_fcache(FmgrInfo *finfo, bool lazyEvalOK)
 static void
 postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 {
-	Snapshot	snapshot;
 	DestReceiver *dest;
 
 	Assert(es->qd == NULL);
 
-	/*
-	 * In a read-only function, use the surrounding query's snapshot;
-	 * otherwise take a new snapshot for each query.  The snapshot should
-	 * include a fresh command ID so that all work to date in this transaction
-	 * is visible.
-	 */
-	if (fcache->readonly_func)
-		snapshot = GetActiveSnapshot();
-	else
-	{
-		CommandCounterIncrement();
-		snapshot = GetTransactionSnapshot();
-	}
+	Assert(ActiveSnapshotSet());
 
 	/*
 	 * If this query produces the function result, send its output to the
@@ -429,13 +433,14 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 	if (IsA(es->stmt, PlannedStmt))
 		es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
 								 fcache->src,
-								 snapshot, InvalidSnapshot,
+								 GetActiveSnapshot(),
+								 InvalidSnapshot,
 								 dest,
 								 fcache->paramLI, 0);
 	else
 		es->qd = CreateUtilityQueryDesc(es->stmt,
 										fcache->src,
-										snapshot,
+										GetActiveSnapshot(),
 										dest,
 										fcache->paramLI);
 
@@ -631,6 +636,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
 	execution_state *es;
 	TupleTableSlot *slot;
 	Datum		result;
+	List		*eslist;
+	ListCell    *eslc;
 
 	/*
 	 * Switch to context in which the fcache lives.  This ensures that
@@ -682,13 +689,13 @@ fmgr_sql(PG_FUNCTION_ARGS)
 		init_sql_fcache(fcinfo->flinfo, lazyEvalOK);
 		fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
 	}
-	es = fcache->func_state;
+	eslist = fcache->func_state;
 
 	/*
 	 * Convert params to appropriate format if starting a fresh execution. (If
 	 * continuing execution, we can re-use prior params.)
 	 */
-	if (es && es->status == F_EXEC_START)
+	if (linitial(eslist) && ((execution_state *) linitial(eslist))->status == F_EXEC_START)
 		postquel_sub_params(fcache, fcinfo);
 
 	/*
@@ -701,8 +708,18 @@ fmgr_sql(PG_FUNCTION_ARGS)
 	/*
 	 * Find first unfinished query in function.
 	 */
-	while (es && es->status == F_EXEC_DONE)
-		es = es->next;
+
+	es = NULL; /* keep compiler quiet */
+	foreach(eslc, eslist)
+	{
+		es = (execution_state *) lfirst(eslc);
+
+		while (es && es->status == F_EXEC_DONE)
+			es = es->next;
+
+		if (es)
+			break;
+	}
 
 	/*
 	 * Execute each command in the function one after another until we either
@@ -713,8 +730,31 @@ fmgr_sql(PG_FUNCTION_ARGS)
 		bool		completed;
 
 		if (es->status == F_EXEC_START)
+		{
+			if (!fcache->readonly_func)
+			{
+				/*
+				 * In a read-only function, use the surrounding query's snapshot;
+				 * otherwise take a new snapshot if we don't have one yet.  The
+				 * snapshot should include a fresh command ID so that all work to
+				 * date in this transaction is visible.
+				 */
+				if (!fcache->snapshot)
+				{
+					CommandCounterIncrement();
+					fcache->snapshot = RegisterSnapshot(GetTransactionSnapshot());
+					PushActiveSnapshot(fcache->snapshot);
+				}
+				else
+					PushUpdatedSnapshot(fcache->snapshot);
+			}
+			
 			postquel_start(es, fcache);
 
+			if (!fcache->readonly_func)
+				PopActiveSnapshot();
+		}
+
 		completed = postquel_getnext(es, fcache);
 
 		/*
@@ -740,6 +780,25 @@ fmgr_sql(PG_FUNCTION_ARGS)
 		if (es->status != F_EXEC_DONE)
 			break;
 		es = es->next;
+
+		if (!es)
+		{
+			eslc = lnext(eslc);
+			if (!eslc)
+				break;
+
+			es = (execution_state *) lfirst(eslc);
+
+			/* make sure we take a new snapshot for this query list */
+			if (!fcache->readonly_func)
+			{
+				Assert(fcache->snapshot != InvalidSnapshot);
+				UnregisterSnapshot(fcache->snapshot);
+				fcache->snapshot = InvalidSnapshot;
+			}
+			else
+				Assert(fcache->snapshot == InvalidSnapshot);
+		}
 	}
 
 	/*
@@ -808,6 +867,11 @@ fmgr_sql(PG_FUNCTION_ARGS)
 											  PointerGetDatum(fcache));
 				fcache->shutdown_reg = false;
 			}
+
+			/* Unregister snapshot if we have one */
+			if (fcache->snapshot != InvalidSnapshot)
+				UnregisterSnapshot(fcache->snapshot);
+			fcache->snapshot = InvalidSnapshot;
 		}
 		else
 		{
@@ -834,6 +898,11 @@ fmgr_sql(PG_FUNCTION_ARGS)
 											  PointerGetDatum(fcache));
 				fcache->shutdown_reg = false;
 			}
+
+			/* Unregister snapshot if we have one */
+			if (fcache->snapshot != InvalidSnapshot)
+				UnregisterSnapshot(fcache->snapshot);
+			fcache->snapshot = InvalidSnapshot;
 		}
 	}
 	else
@@ -864,6 +933,11 @@ fmgr_sql(PG_FUNCTION_ARGS)
 
 		/* Clear the tuplestore, but keep it for next time */
 		tuplestore_clear(fcache->tstore);
+
+		/* Unregister snapshot if we have one */
+		if (fcache->snapshot != InvalidSnapshot)
+			UnregisterSnapshot(fcache->snapshot);
+		fcache->snapshot = InvalidSnapshot;
 	}
 
 	/*
@@ -872,11 +946,14 @@ fmgr_sql(PG_FUNCTION_ARGS)
 	 */
 	if (es == NULL)
 	{
-		es = fcache->func_state;
-		while (es)
+		foreach(eslc, fcache->func_state)
 		{
-			es->status = F_EXEC_START;
-			es = es->next;
+			es = (execution_state *) lfirst(eslc);
+			while (es)
+			{
+				es->status = F_EXEC_START;
+				es = es->next;
+			}
 		}
 	}
 
@@ -927,19 +1004,24 @@ sql_exec_error_callback(void *arg)
 	{
 		execution_state *es;
 		int			query_num;
+		ListCell   *lc;
 
-		es = fcache->func_state;
+		es = NULL; /* keep compiler quiet */
 		query_num = 1;
-		while (es)
+		foreach(lc, fcache->func_state)
 		{
-			if (es->qd)
+			es = (execution_state *) lfirst(lc);
+			while (es)
 			{
-				errcontext("SQL function \"%s\" statement %d",
-						   fcache->fname, query_num);
-				break;
+				if (es->qd)
+				{
+					errcontext("SQL function \"%s\" statement %d",
+							   fcache->fname, query_num);
+					break;
+				}
+				es = es->next;
+				query_num++;
 			}
-			es = es->next;
-			query_num++;
 		}
 		if (es == NULL)
 		{
@@ -970,18 +1052,29 @@ static void
 ShutdownSQLFunction(Datum arg)
 {
 	SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) DatumGetPointer(arg);
-	execution_state *es = fcache->func_state;
+	execution_state *es;
+	ListCell		*lc;
 
-	while (es != NULL)
+	foreach(lc, fcache->func_state)
 	{
-		/* Shut down anything still running */
-		if (es->status == F_EXEC_RUN)
-			postquel_end(es);
-		/* Reset states to START in case we're called again */
-		es->status = F_EXEC_START;
-		es = es->next;
+		es = (execution_state *) lfirst(lc);
+
+		while (es)
+		{
+			/* Shut down anything still running */
+			if (es->status == F_EXEC_RUN)
+				postquel_end(es);
+			/* Reset states to START in case we're called again */
+			es->status = F_EXEC_START;
+			es = es->next;
+		}
 	}
 
+	/* Unregister snapshot if we have one */
+	if (fcache->snapshot != InvalidSnapshot)
+		UnregisterSnapshot(fcache->snapshot);
+	fcache->snapshot = InvalidSnapshot;
+
 	/* Release tuplestore if we have one */
 	if (fcache->tstore)
 		tuplestore_end(fcache->tstore);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index c579017..2998b3a 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1769,6 +1769,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 	SPITupleTable *my_tuptable = NULL;
 	int			res = 0;
 	bool		have_active_snap = ActiveSnapshotSet();
+	bool		registered_snap = false;
 	ErrorContextCallback spierrcontext;
 	CachedPlan *cplan = NULL;
 	ListCell   *lc1;
@@ -1872,8 +1873,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 				}
 				else
 				{
-					PushActiveSnapshot(GetTransactionSnapshot());
+					snapshot = RegisterSnapshot(GetTransactionSnapshot());
+					PushActiveSnapshot(snapshot);
 					pushed_active_snap = true;
+					registered_snap = true;
 				}
 			}
 			else
@@ -1966,10 +1969,23 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 */
 		if (!read_only)
 			CommandCounterIncrement();
+
+		/*
+		 * If we took a new snapshot for this query list, unregister it and
+		 * make sure we take a new one for the next list.
+		 */
+		if (registered_snap)
+		{
+			UnregisterSnapshot(snapshot);
+			snapshot = InvalidSnapshot;
+		}
 	}
 
 fail:
 
+	if (registered_snap)
+		UnregisterSnapshot(snapshot);
+
 	/* We no longer need the cached plan refcount, if any */
 	if (cplan)
 		ReleaseCachedPlan(cplan, true);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8eb02da..b475e11 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -170,11 +170,6 @@ ProcessQuery(PlannedStmt *plan,
 	elog(DEBUG3, "ProcessQuery");
 
 	/*
-	 * Must always set a snapshot for plannable queries.
-	 */
-	PushActiveSnapshot(GetTransactionSnapshot());
-
-	/*
 	 * Create the QueryDesc object
 	 */
 	queryDesc = CreateQueryDesc(plan, sourceText,
@@ -234,8 +229,6 @@ ProcessQuery(PlannedStmt *plan,
 	/* Now take care of any queued AFTER triggers */
 	AfterTriggerEndQuery(queryDesc->estate);
 
-	PopActiveSnapshot();
-
 	/*
 	 * Now, we close down all the scans and free allocated resources.
 	 */
@@ -1220,6 +1213,7 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 			   char *completionTag)
 {
 	ListCell   *stmtlist_item;
+	Snapshot	snapshot = InvalidSnapshot;
 
 	/*
 	 * If the destination is DestRemoteExecute, change to DestNone.  The
@@ -1262,6 +1256,15 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 			if (log_executor_stats)
 				ResetUsage();
 
+			/* if no snapshot is set, grab a new one and register it */
+			if (snapshot == InvalidSnapshot)
+			{
+				snapshot = RegisterSnapshot(GetTransactionSnapshot());
+				PushActiveSnapshot(snapshot);
+			}
+			else
+				PushUpdatedSnapshot(snapshot);
+
 			if (pstmt->canSetTag)
 			{
 				/* statement can set tag string */
@@ -1279,6 +1282,8 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 							 altdest, NULL);
 			}
 
+			PopActiveSnapshot();
+
 			if (log_executor_stats)
 				ShowUsage("EXECUTOR STATISTICS");
 
@@ -1291,11 +1296,25 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 			 *
 			 * These are assumed canSetTag if they're the only stmt in the
 			 * portal.
+			 *
+			 * NotifyStmt is the only utility statement allowed in a list of
+			 * rewritten queries, and it doesn't need a snapshot so we don't
+			 * need to worry about it.  However, if the list has only one
+			 * statement and it's a utility statement, we are not allowed to
+			 * take a snapshot.  See the first comment in PortalRunUtility().
 			 */
 			if (list_length(portal->stmts) == 1)
+			{
+				Assert(snapshot == InvalidSnapshot);
+
 				PortalRunUtility(portal, stmt, isTopLevel, dest, completionTag);
+			}
 			else
+			{
+				Assert(IsA(stmt, NotifyStmt));
+
 				PortalRunUtility(portal, stmt, isTopLevel, altdest, NULL);
+			}
 		}
 
 		/*
@@ -1313,6 +1332,9 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 		MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
 	}
 
+	if (snapshot != InvalidSnapshot)
+		UnregisterSnapshot(snapshot);
+
 	/*
 	 * If a command completion tag was supplied, use it.  Otherwise use the
 	 * portal's commandTag as the default completion tag.
-- 
1.7.1