aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/namespace.c98
-rw-r--r--src/backend/utils/cache/plancache.c154
-rw-r--r--src/backend/utils/resowner/resowner.c24
3 files changed, 266 insertions, 10 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff78248e0d..2ec23016fe5 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -126,6 +126,11 @@
* namespaceUser is the userid the path has been computed for.
*
* Note: all data pointed to by these List variables is in TopMemoryContext.
+ *
+ * activePathGeneration is incremented whenever the effective values of
+ * activeSearchPath/activeCreationNamespace/activeTempCreationPending change.
+ * This can be used to quickly detect whether any change has happened since
+ * a previous examination of the search path state.
*/
/* These variables define the actually active state: */
@@ -138,6 +143,9 @@ static Oid activeCreationNamespace = InvalidOid;
/* if true, activeCreationNamespace is wrong, it should be temp namespace */
static bool activeTempCreationPending = false;
+/* current generation counter; make sure this is never zero */
+static uint64 activePathGeneration = 1;
+
/* These variables are the values last derived from namespace_search_path: */
static List *baseSearchPath = NIL;
@@ -3373,6 +3381,7 @@ GetOverrideSearchPath(MemoryContext context)
schemas = list_delete_first(schemas);
}
result->schemas = schemas;
+ result->generation = activePathGeneration;
MemoryContextSwitchTo(oldcxt);
@@ -3393,12 +3402,18 @@ CopyOverrideSearchPath(OverrideSearchPath *path)
result->schemas = list_copy(path->schemas);
result->addCatalog = path->addCatalog;
result->addTemp = path->addTemp;
+ result->generation = path->generation;
return result;
}
/*
* OverrideSearchPathMatchesCurrent - does path match current setting?
+ *
+ * This is tested over and over in some common code paths, and in the typical
+ * scenario where the active search path seldom changes, it'll always succeed.
+ * We make that case fast by keeping a generation counter that is advanced
+ * whenever the active search path changes.
*/
bool
OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
@@ -3408,6 +3423,10 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
recomputeNamespacePath();
+ /* Quick out if already known equal to active path. */
+ if (path->generation == activePathGeneration)
+ return true;
+
/* We scan down the activeSearchPath to see if it matches the input. */
lc = list_head(activeSearchPath);
@@ -3440,6 +3459,13 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
}
if (lc)
return false;
+
+ /*
+ * Update path->generation so that future tests will return quickly, so
+ * long as the active search path doesn't change.
+ */
+ path->generation = activePathGeneration;
+
return true;
}
@@ -3510,6 +3536,14 @@ PushOverrideSearchPath(OverrideSearchPath *newpath)
activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */
+ /*
+ * We always increment activePathGeneration when pushing/popping an
+ * override path. In current usage, these actions always change the
+ * effective path state, so there's no value in checking to see if it
+ * didn't change.
+ */
+ activePathGeneration++;
+
MemoryContextSwitchTo(oldcxt);
}
@@ -3551,6 +3585,9 @@ PopOverrideSearchPath(void)
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
}
+
+ /* As above, the generation always increments. */
+ activePathGeneration++;
}
@@ -3707,6 +3744,7 @@ recomputeNamespacePath(void)
ListCell *l;
bool temp_missing;
Oid firstNS;
+ bool pathChanged;
MemoryContext oldcxt;
/* Do nothing if an override search spec is active. */
@@ -3814,18 +3852,31 @@ recomputeNamespacePath(void)
oidlist = lcons_oid(myTempNamespace, oidlist);
/*
- * Now that we've successfully built the new list of namespace OIDs, save
- * it in permanent storage.
+ * We want to detect the case where the effective value of the base search
+ * path variables didn't change. As long as we're doing so, we can avoid
+ * copying the OID list unncessarily.
*/
- oldcxt = MemoryContextSwitchTo(TopMemoryContext);
- newpath = list_copy(oidlist);
- MemoryContextSwitchTo(oldcxt);
+ if (baseCreationNamespace == firstNS &&
+ baseTempCreationPending == temp_missing &&
+ equal(oidlist, baseSearchPath))
+ {
+ pathChanged = false;
+ }
+ else
+ {
+ pathChanged = true;
+
+ /* Must save OID list in permanent storage. */
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ newpath = list_copy(oidlist);
+ MemoryContextSwitchTo(oldcxt);
- /* Now safe to assign to state variables. */
- list_free(baseSearchPath);
- baseSearchPath = newpath;
- baseCreationNamespace = firstNS;
- baseTempCreationPending = temp_missing;
+ /* Now safe to assign to state variables. */
+ list_free(baseSearchPath);
+ baseSearchPath = newpath;
+ baseCreationNamespace = firstNS;
+ baseTempCreationPending = temp_missing;
+ }
/* Mark the path valid. */
baseSearchPathValid = true;
@@ -3836,6 +3887,16 @@ recomputeNamespacePath(void)
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ /*
+ * Bump the generation only if something actually changed. (Notice that
+ * what we compared to was the old state of the base path variables; so
+ * this does not deal with the situation where we have just popped an
+ * override path and restored the prior state of the base path. Instead
+ * we rely on the override-popping logic to have bumped the generation.)
+ */
+ if (pathChanged)
+ activePathGeneration++;
+
/* Clean up. */
pfree(rawname);
list_free(namelist);
@@ -4054,6 +4115,8 @@ AtEOXact_Namespace(bool isCommit, bool parallel)
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ /* Always bump generation --- see note in recomputeNamespacePath */
+ activePathGeneration++;
}
}
@@ -4109,6 +4172,8 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
overrideStack = list_delete_first(overrideStack);
list_free(entry->searchPath);
pfree(entry);
+ /* Always bump generation --- see note in recomputeNamespacePath */
+ activePathGeneration++;
}
/* Activate the next level down. */
@@ -4118,6 +4183,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
activeSearchPath = entry->searchPath;
activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */
+
+ /*
+ * It's probably unnecessary to bump generation here, but this should
+ * not be a performance-critical case, so better to be over-cautious.
+ */
+ activePathGeneration++;
}
else
{
@@ -4125,6 +4196,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+
+ /*
+ * If we popped an override stack entry, then we already bumped the
+ * generation above. If we did not, then the above assignments did
+ * nothing and we need not bump the generation.
+ */
}
}
@@ -4264,6 +4341,7 @@ InitializeSearchPath(void)
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ activePathGeneration++; /* pro forma */
}
else
{
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index dbae18d68c6..8e27b03cbb6 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -1278,6 +1278,160 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
}
/*
+ * CachedPlanAllowsSimpleValidityCheck: can we use CachedPlanIsSimplyValid?
+ *
+ * This function, together with CachedPlanIsSimplyValid, provides a fast path
+ * for revalidating "simple" generic plans. The core requirement to be simple
+ * is that the plan must not require taking any locks, which translates to
+ * not touching any tables; this happens to match up well with an important
+ * use-case in PL/pgSQL. This function tests whether that's true, along
+ * with checking some other corner cases that we'd rather not bother with
+ * handling in the fast path. (Note that it's still possible for such a plan
+ * to be invalidated, for example due to a change in a function that was
+ * inlined into the plan.)
+ *
+ * This must only be called on known-valid generic plans (eg, ones just
+ * returned by GetCachedPlan). If it returns true, the caller may re-use
+ * the cached plan as long as CachedPlanIsSimplyValid returns true; that
+ * check is much cheaper than the full revalidation done by GetCachedPlan.
+ * Nonetheless, no required checks are omitted.
+ */
+bool
+CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
+ CachedPlan *plan)
+{
+ ListCell *lc;
+
+ /* Sanity-check that the caller gave us a validated generic plan. */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+ Assert(plansource->is_valid);
+ Assert(plan->is_valid);
+ Assert(plan == plansource->gplan);
+
+ /* We don't support oneshot plans here. */
+ if (plansource->is_oneshot)
+ return false;
+ Assert(!plan->is_oneshot);
+
+ /*
+ * If the plan is dependent on RLS considerations, or it's transient,
+ * reject. These things probably can't ever happen for table-free
+ * queries, but for safety's sake let's check.
+ */
+ if (plansource->dependsOnRLS)
+ return false;
+ if (plan->dependsOnRole)
+ return false;
+ if (TransactionIdIsValid(plan->saved_xmin))
+ return false;
+
+ /*
+ * Reject if AcquirePlannerLocks would have anything to do. This is
+ * simplistic, but there's no need to inquire any more carefully; indeed,
+ * for current callers it shouldn't even be possible to hit any of these
+ * checks.
+ */
+ foreach(lc, plansource->query_list)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ if (query->commandType == CMD_UTILITY)
+ return false;
+ if (query->rtable || query->cteList || query->hasSubLinks)
+ return false;
+ }
+
+ /*
+ * Reject if AcquireExecutorLocks would have anything to do. This is
+ * probably unnecessary given the previous check, but let's be safe.
+ */
+ foreach(lc, plan->stmt_list)
+ {
+ PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
+ ListCell *lc2;
+
+ if (plannedstmt->commandType == CMD_UTILITY)
+ return false;
+
+ /*
+ * We have to grovel through the rtable because it's likely to contain
+ * an RTE_RESULT relation, rather than being totally empty.
+ */
+ foreach(lc2, plannedstmt->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
+
+ if (rte->rtekind == RTE_RELATION)
+ return false;
+ }
+ }
+
+ /*
+ * Okay, it's simple. Note that what we've primarily established here is
+ * that no locks need be taken before checking the plan's is_valid flag.
+ */
+ return true;
+}
+
+/*
+ * CachedPlanIsSimplyValid: quick check for plan still being valid
+ *
+ * This function must not be used unless CachedPlanAllowsSimpleValidityCheck
+ * previously said it was OK.
+ *
+ * If the plan is valid, and "owner" is not NULL, record a refcount on
+ * the plan in that resowner before returning. It is caller's responsibility
+ * to be sure that a refcount is held on any plan that's being actively used.
+ *
+ * The code here is unconditionally safe as long as the only use of this
+ * CachedPlanSource is in connection with the particular CachedPlan pointer
+ * that's passed in. If the plansource were being used for other purposes,
+ * it's possible that its generic plan could be invalidated and regenerated
+ * while the current caller wasn't looking, and then there could be a chance
+ * collision of address between this caller's now-stale plan pointer and the
+ * actual address of the new generic plan. For current uses, that scenario
+ * can't happen; but with a plansource shared across multiple uses, it'd be
+ * advisable to also save plan->generation and verify that that still matches.
+ */
+bool
+CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan,
+ ResourceOwner owner)
+{
+ /*
+ * Careful here: since the caller doesn't necessarily hold a refcount on
+ * the plan to start with, it's possible that "plan" is a dangling
+ * pointer. Don't dereference it until we've verified that it still
+ * matches the plansource's gplan (which is either valid or NULL).
+ */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+ /*
+ * Has cache invalidation fired on this plan? We can check this right
+ * away since there are no locks that we'd need to acquire first.
+ */
+ if (!plansource->is_valid || plan != plansource->gplan || !plan->is_valid)
+ return false;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+
+ /* Is the search_path still the same as when we made it? */
+ Assert(plansource->search_path != NULL);
+ if (!OverrideSearchPathMatchesCurrent(plansource->search_path))
+ return false;
+
+ /* It's still good. Bump refcount if requested. */
+ if (owner)
+ {
+ ResourceOwnerEnlargePlanCacheRefs(owner);
+ plan->refcount++;
+ ResourceOwnerRememberPlanCacheRef(owner, plan);
+ }
+
+ return true;
+}
+
+/*
* CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
*
* This can only be applied to unsaved plans; once saved, a plan always
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 3c39e48825a..8bc2c4e9ea3 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -679,6 +679,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
}
/*
+ * ResourceOwnerReleaseAllPlanCacheRefs
+ * Release the plancache references (only) held by this owner.
+ *
+ * We might eventually add similar functions for other resource types,
+ * but for now, only this is needed.
+ */
+void
+ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
+{
+ ResourceOwner save;
+ Datum foundres;
+
+ save = CurrentResourceOwner;
+ CurrentResourceOwner = owner;
+ while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+ {
+ CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+
+ ReleaseCachedPlan(res, true);
+ }
+ CurrentResourceOwner = save;
+}
+
+/*
* ResourceOwnerDelete
* Delete an owner object and its descendants.
*