* and implementing search-path-controlled searches.
*
*
- * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
*/
#include "postgres.h"
+#include "access/htup_details.h"
+#include "access/parallel.h"
#include "access/xact.h"
+#include "access/xlog.h"
#include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "storage/sinval.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
* when we are obeying an override search path spec that says not to use the
* temp namespace, or the temp namespace is included in the explicit list.)
*
- * 2. The system catalog namespace is always searched. If the system
+ * 2. The system catalog namespace is always searched. If the system
* namespace is present in the explicit path then it will be searched in
* the specified order; otherwise it will be searched after TEMP tables and
- * *before* the explicit list. (It might seem that the system namespace
+ * *before* the explicit list. (It might seem that the system namespace
* should be implicitly last, but this behavior appears to be required by
* SQL99. Also, this provides a way to search the system namespace first
* without thereby making it the default creation target namespace.)
* to refer to the current backend's temp namespace. This is usually also
* ignorable if the temp namespace hasn't been set up, but there's a special
* case: if "pg_temp" appears first then it should be the default creation
- * target. We kluge this case a little bit so that the temp namespace isn't
+ * target. We kluge this case a little bit so that the temp namespace isn't
* set up until the first attempt to create something in it. (The reason for
* klugery is that we can't create the temp namespace outside a transaction,
* but initial GUC processing of search_path happens outside a transaction.)
* In bootstrap mode, the search path is set equal to "pg_catalog", so that
* the system namespace is the only one searched or inserted into.
* initdb is also careful to set search_path to "pg_catalog" for its
- * post-bootstrap standalone backend runs. Otherwise the default search
+ * post-bootstrap standalone backend runs. Otherwise the default search
* path is determined by GUC. The factory default path contains the PUBLIC
* namespace (if it exists), preceded by the user's personal namespace
* (if one exists).
/*
* myTempNamespace is InvalidOid until and unless a TEMP namespace is set up
* in a particular backend session (this happens when a CREATE TEMP TABLE
- * command is first executed). Thereafter it's the OID of the temp namespace.
+ * command is first executed). Thereafter it's the OID of the temp namespace.
*
* myTempToastNamespace is the OID of the namespace for my temp tables' toast
- * tables. It is set when myTempNamespace is, and is InvalidOid before that.
+ * tables. It is set when myTempNamespace is, and is InvalidOid before that.
*
* myTempNamespaceSubID shows whether we've created the TEMP namespace in the
- * current subtransaction. The flag propagates up the subtransaction tree,
+ * current subtransaction. The flag propagates up the subtransaction tree,
* so the main transaction will correctly recognize the flag if all
* intermediate subtransactions commit. When it is InvalidSubTransactionId,
* we either haven't made the TEMP namespace yet, or have successfully
* Given a RangeVar describing an existing relation,
* select the proper namespace and look up the relation OID.
*
- * If the relation is not found, return InvalidOid if missing_ok = true,
- * otherwise raise an error.
+ * If the schema or relation is not found, return InvalidOid if missing_ok
+ * = true, otherwise raise an error.
*
* If nowait = true, throw an error if we'd have to wait for a lock.
+ *
+ * Callback allows caller to check permissions or acquire additional locks
+ * prior to grabbing the relation lock.
*/
Oid
-RangeVarGetRelid(const RangeVar *relation, LOCKMODE lockmode, bool missing_ok,
- bool nowait)
+RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
+ bool missing_ok, bool nowait,
+ RangeVarGetRelidCallback callback, void *callback_arg)
{
uint64 inval_count;
- Oid namespaceId;
Oid relId;
Oid oldRelId = InvalidOid;
bool retry = false;
}
/*
- * DDL operations can change the results of a name lookup. Since all
- * such operations will generate invalidation messages, we keep track
- * of whether any such messages show up while we're performing the
- * operation, and retry until either (1) no more invalidation messages
- * show up or (2) the answer doesn't change.
+ * DDL operations can change the results of a name lookup. Since all such
+ * operations will generate invalidation messages, we keep track of
+ * whether any such messages show up while we're performing the operation,
+ * and retry until either (1) no more invalidation messages show up or (2)
+ * the answer doesn't change.
*
* But if lockmode = NoLock, then we assume that either the caller is OK
* with the answer changing under them, or that they already hold some
* appropriate lock, and therefore return the first answer we get without
* checking for invalidation messages. Also, if the requested lock is
- * already held, no LockRelationOid will not AcceptInvalidationMessages,
- * so we may fail to notice a change. We could protect against that case
- * by calling AcceptInvalidationMessages() before beginning this loop,
- * but that would add a significant amount overhead, so for now we don't.
+ * already held, LockRelationOid will not AcceptInvalidationMessages, so
+ * we may fail to notice a change. We could protect against that case by
+ * calling AcceptInvalidationMessages() before beginning this loop, but
+ * that would add a significant amount overhead, so for now we don't.
*/
for (;;)
{
*/
if (relation->relpersistence == RELPERSISTENCE_TEMP)
{
- if (relation->schemaname)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("temporary tables cannot specify a schema name")));
- if (OidIsValid(myTempNamespace))
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid; /* this probably can't happen? */
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existant schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name")));
+ }
+
relId = get_relname_relid(relation->relname, myTempNamespace);
- else /* this probably can't happen? */
- relId = InvalidOid;
+ }
}
else if (relation->schemaname)
{
+ Oid namespaceId;
+
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(relation->schemaname);
- relId = get_relname_relid(relation->relname, namespaceId);
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
}
else
{
relId = RelnameGetRelid(relation->relname);
}
+ /*
+ * Invoke caller-supplied callback, if any.
+ *
+ * This callback is a good place to check permissions: we haven't
+ * taken the table lock yet (and it's really best to check permissions
+ * before locking anything!), but we've gotten far enough to know what
+ * OID we think we should lock. Of course, concurrent DDL might
+ * change things while we're waiting for the lock, but in that case
+ * the callback will be invoked again for the new OID.
+ */
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
/*
* If no lock requested, we assume the caller knows what they're
* doing. They should have already acquired a heavyweight lock on
- * this relation earlier in the processing of this same statement,
- * so it wouldn't be appropriate to AcceptInvalidationMessages()
- * here, as that might pull the rug out from under them.
+ * this relation earlier in the processing of this same statement, so
+ * it wouldn't be appropriate to AcceptInvalidationMessages() here, as
+ * that might pull the rug out from under them.
*/
if (lockmode == NoLock)
break;
/*
- * If, upon retry, we get back the same OID we did last time, then
- * the invalidation messages we processed did not change the final
- * answer. So we're done.
+ * If, upon retry, we get back the same OID we did last time, then the
+ * invalidation messages we processed did not change the final answer.
+ * So we're done.
+ *
+ * If we got a different OID, we've locked the relation that used to
+ * have this name rather than the one that does now. So release the
+ * lock.
*/
- if (retry && relId == oldRelId)
- break;
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
/*
* Lock relation. This will also accept any pending invalidation
break;
/*
- * Something may have changed. Let's repeat the name lookup, to
- * make sure this name still references the same relation it did
+ * Something may have changed. Let's repeat the name lookup, to make
+ * sure this name still references the same relation it did
* previously.
*/
retry = true;
/*
* RangeVarGetAndCheckCreationNamespace
- * As RangeVarGetCreationNamespace, but with a permissions check.
+ *
+ * This function returns the OID of the namespace in which a new relation
+ * with a given name should be created. If the user does not have CREATE
+ * permission on the target namespace, this function will instead signal
+ * an ERROR.
+ *
+ * If non-NULL, *existing_oid is set to the OID of any existing relation with
+ * the same name which already exists in that namespace, or to InvalidOid if
+ * no such relation exists.
+ *
+ * If lockmode != NoLock, the specified lock mode is acquired on the existing
+ * relation, if any, provided that the current user owns the target relation.
+ * However, if lockmode != NoLock and the user does not own the target
+ * relation, we throw an ERROR, as we must not try to lock relations the
+ * user does not have permissions on.
+ *
+ * As a side effect, this function acquires AccessShareLock on the target
+ * namespace. Without this, the namespace could be dropped before our
+ * transaction commits, leaving behind relations with relnamespace pointing
+ * to a no-longer-exstant namespace.
+ *
+ * As a further side-effect, if the select namespace is a temporary namespace,
+ * we mark the RangeVar as RELPERSISTENCE_TEMP.
*/
Oid
-RangeVarGetAndCheckCreationNamespace(const RangeVar *newRelation)
+RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
+ LOCKMODE lockmode,
+ Oid *existing_relation_id)
{
- Oid namespaceId;
+ uint64 inval_count;
+ Oid relid;
+ Oid oldrelid = InvalidOid;
+ Oid nspid;
+ Oid oldnspid = InvalidOid;
+ bool retry = false;
- namespaceId = RangeVarGetCreationNamespace(newRelation);
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname)));
+ }
/*
- * Check we have permission to create there. Skip check if bootstrapping,
- * since permissions machinery may not be working yet.
+ * As in RangeVarGetRelidExtended(), we guard against concurrent DDL
+ * operations by tracking whether any invalidation messages are processed
+ * while we're doing the name lookups and acquiring locks. See comments
+ * in that function for a more detailed explanation of this logic.
*/
- if (!IsBootstrapProcessingMode())
+ for (;;)
{
AclResult aclresult;
- aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
- ACL_CREATE);
+ inval_count = SharedInvalidMessageCounter;
+
+ /* Look up creation namespace and check for existing relation. */
+ nspid = RangeVarGetCreationNamespace(relation);
+ Assert(OidIsValid(nspid));
+ if (existing_relation_id != NULL)
+ relid = get_relname_relid(relation->relname, nspid);
+ else
+ relid = InvalidOid;
+
+ /*
+ * In bootstrap processing mode, we don't bother with permissions or
+ * locking. Permissions might not be working yet, and locking is
+ * unnecessary.
+ */
+ if (IsBootstrapProcessingMode())
+ break;
+
+ /* Check namespace permissions. */
+ aclresult = pg_namespace_aclcheck(nspid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
- get_namespace_name(namespaceId));
+ get_namespace_name(nspid));
+
+ if (retry)
+ {
+ /* If nothing changed, we're done. */
+ if (relid == oldrelid && nspid == oldnspid)
+ break;
+ /* If creation namespace has changed, give up old lock. */
+ if (nspid != oldnspid)
+ UnlockDatabaseObject(NamespaceRelationId, oldnspid, 0,
+ AccessShareLock);
+ /* If name points to something different, give up old lock. */
+ if (relid != oldrelid && OidIsValid(oldrelid) && lockmode != NoLock)
+ UnlockRelationOid(oldrelid, lockmode);
+ }
+
+ /* Lock namespace. */
+ if (nspid != oldnspid)
+ LockDatabaseObject(NamespaceRelationId, nspid, 0, AccessShareLock);
+
+ /* Lock relation, if required if and we have permission. */
+ if (lockmode != NoLock && OidIsValid(relid))
+ {
+ if (!pg_class_ownercheck(relid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+ relation->relname);
+ if (relid != oldrelid)
+ LockRelationOid(relid, lockmode);
+ }
+
+ /* If no invalidation message were processed, we're done! */
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ /* Something may have changed, so recheck our work. */
+ retry = true;
+ oldrelid = relid;
+ oldnspid = nspid;
}
- return namespaceId;
+ RangeVarAdjustRelationPersistence(relation, nspid);
+ if (existing_relation_id != NULL)
+ *existing_relation_id = relid;
+ return nspid;
}
/*
switch (newRelation->relpersistence)
{
case RELPERSISTENCE_TEMP:
- if (!isTempOrToastNamespace(nspid))
+ if (!isTempOrTempToastNamespace(nspid))
{
if (isAnyTempNamespace(nspid))
ereport(ERROR,
}
break;
case RELPERSISTENCE_PERMANENT:
- if (isTempOrToastNamespace(nspid))
+ if (isTempOrTempToastNamespace(nspid))
newRelation->relpersistence = RELPERSISTENCE_TEMP;
else if (isAnyTempNamespace(nspid))
ereport(ERROR,
* and the returned nvargs will always be zero.
*
* If expand_defaults is true, functions that could match after insertion of
- * default argument values will also be retrieved. In this case the returned
+ * default argument values will also be retrieved. In this case the returned
* structs could have nargs > passed-in nargs, and ndargs is set to the number
* of additional args (which can be retrieved from the function's
* proargdefaults entry).
* with oid = 0 that represents a set of such conflicting candidates.
* The caller might end up discarding such an entry anyway, but if it selects
* such an entry it should react as though the call were ambiguous.
+ *
+ * If missing_ok is true, an empty list (NULL) is returned if the name was
+ * schema- qualified with a schema that does not exist. Likewise if no
+ * candidate is found for other reasons.
*/
FuncCandidateList
FuncnameGetCandidates(List *names, int nargs, List *argnames,
- bool expand_variadic, bool expand_defaults)
+ bool expand_variadic, bool expand_defaults,
+ bool missing_ok)
{
FuncCandidateList resultList = NULL;
bool any_special = false;
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (!OidIsValid(namespaceId))
+ return NULL;
}
else
{
* Call uses positional notation
*
* Check if function is variadic, and get variadic element type if
- * so. If expand_variadic is false, we should just ignore
+ * so. If expand_variadic is false, we should just ignore
* variadic-ness.
*/
if (pronargs <= nargs && expand_variadic)
*/
effective_nargs = Max(pronargs, nargs);
newResult = (FuncCandidateList)
- palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
- + effective_nargs * sizeof(Oid));
+ palloc(offsetof(struct _FuncCandidateList, args) +
+ effective_nargs * sizeof(Oid));
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(proctup);
newResult->nargs = effective_nargs;
if (prevResult)
{
/*
- * We have a match with a previous result. Decide which one
+ * We have a match with a previous result. Decide which one
* to keep, or mark it ambiguous if we can't decide. The
* logic here is preference > 0 means prefer the old result,
* preference < 0 means prefer the new, preference = 0 means
visible = false;
clist = FuncnameGetCandidates(list_make1(makeString(proname)),
- nargs, NIL, false, false);
+ nargs, NIL, false, false, false);
for (; clist; clist = clist->next)
{
* a postfix op.
*
* If the operator name is not schema-qualified, it is sought in the current
- * namespace search path.
+ * namespace search path. If the name is schema-qualified and the given
+ * schema does not exist, InvalidOid is returned.
*/
Oid
OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
{
/* search only in exact schema given */
Oid namespaceId;
- HeapTuple opertup;
- namespaceId = LookupExplicitNamespace(schemaname);
- opertup = SearchSysCache4(OPERNAMENSP,
- CStringGetDatum(opername),
- ObjectIdGetDatum(oprleft),
- ObjectIdGetDatum(oprright),
- ObjectIdGetDatum(namespaceId));
- if (HeapTupleIsValid(opertup))
+ namespaceId = LookupExplicitNamespace(schemaname, true);
+ if (OidIsValid(namespaceId))
{
- Oid result = HeapTupleGetOid(opertup);
+ HeapTuple opertup;
- ReleaseSysCache(opertup);
- return result;
+ opertup = SearchSysCache4(OPERNAMENSP,
+ CStringGetDatum(opername),
+ ObjectIdGetDatum(oprleft),
+ ObjectIdGetDatum(oprright),
+ ObjectIdGetDatum(namespaceId));
+ if (HeapTupleIsValid(opertup))
+ {
+ Oid result = HeapTupleGetOid(opertup);
+
+ ReleaseSysCache(opertup);
+ return result;
+ }
}
+
return InvalidOid;
}
* identical entries in later namespaces.
*
* The returned items always have two args[] entries --- one or the other
- * will be InvalidOid for a prefix or postfix oprkind. nargs is 2, too.
+ * will be InvalidOid for a prefix or postfix oprkind. nargs is 2, too.
*/
FuncCandidateList
-OpernameGetCandidates(List *names, char oprkind)
+OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
{
FuncCandidateList resultList = NULL;
char *resultSpace = NULL;
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
+ namespaceId = LookupExplicitNamespace(schemaname, missing_schema_ok);
+ if (missing_schema_ok && !OidIsValid(namespaceId))
+ return NULL;
}
else
{
* separate palloc for each operator, but profiling revealed that the
* pallocs used an unreasonably large fraction of parsing time.
*/
-#define SPACE_PER_OP MAXALIGN(sizeof(struct _FuncCandidateList) + sizeof(Oid))
+#define SPACE_PER_OP MAXALIGN(offsetof(struct _FuncCandidateList, args) + \
+ 2 * sizeof(Oid))
if (catlist->n_members > 0)
resultSpace = palloc(catlist->n_members * SPACE_PER_OP);
* If it is in the path, it might still not be visible; it could be
* hidden by another operator of the same name and arguments earlier
* in the path. So we must do a slow check to see if this is the same
- * operator that would be found by OpernameGetOprId.
+ * operator that would be found by OpernameGetOprid.
*/
char *oprname = NameStr(oprform->oprname);
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
- prsoid = GetSysCacheOid2(TSPARSERNAMENSP,
- PointerGetDatum(parser_name),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ prsoid = InvalidOid;
+ else
+ prsoid = GetSysCacheOid2(TSPARSERNAMENSP,
+ PointerGetDatum(parser_name),
+ ObjectIdGetDatum(namespaceId));
}
else
{
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
- dictoid = GetSysCacheOid2(TSDICTNAMENSP,
- PointerGetDatum(dict_name),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ dictoid = InvalidOid;
+ else
+ dictoid = GetSysCacheOid2(TSDICTNAMENSP,
+ PointerGetDatum(dict_name),
+ ObjectIdGetDatum(namespaceId));
}
else
{
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
- tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP,
- PointerGetDatum(template_name),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ tmploid = InvalidOid;
+ else
+ tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP,
+ PointerGetDatum(template_name),
+ ObjectIdGetDatum(namespaceId));
}
else
{
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
- cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP,
- PointerGetDatum(config_name),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ cfgoid = InvalidOid;
+ else
+ cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP,
+ PointerGetDatum(config_name),
+ ObjectIdGetDatum(namespaceId));
}
else
{
/*
* TSConfigIsVisible
* Determine whether a text search configuration (identified by OID)
- * is visible in the current search path. Visible means "would be found
+ * is visible in the current search path. Visible means "would be found
* by searching for the unqualified text search configuration name".
*/
bool
if (strcmp(nspname, "pg_temp") == 0)
{
if (OidIsValid(myTempNamespace))
+ {
+ InvokeNamespaceSearchHook(myTempNamespace, true);
return myTempNamespace;
+ }
/*
* Since this is used only for looking up existing objects, there is
* Process an explicitly-specified schema name: look up the schema
* and verify we have USAGE (lookup) rights in it.
*
- * Returns the namespace OID. Raises ereport if any problem.
+ * Returns the namespace OID
*/
Oid
-LookupExplicitNamespace(const char *nspname)
+LookupExplicitNamespace(const char *nspname, bool missing_ok)
{
Oid namespaceId;
AclResult aclresult;
/*
* Since this is used only for looking up existing objects, there is
* no point in trying to initialize the temp namespace here; and doing
- * so might create problems for some callers. Just fall through and
- * give the "does not exist" error.
+ * so might create problems for some callers --- just fall through.
*/
}
- namespaceId = get_namespace_oid(nspname, false);
+ namespaceId = get_namespace_oid(nspname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ return InvalidOid;
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
nspname);
+ /* Schema search hook for this lookup */
+ InvokeNamespaceSearchHook(namespaceId, true);
return namespaceId;
}
/*
* Common checks on switching namespaces.
*
- * We complain if (1) the old and new namespaces are the same, (2) either the
- * old or new namespaces is a temporary schema (or temporary toast schema), or
- * (3) either the old or new namespaces is the TOAST schema.
+ * We complain if either the old or new namespaces is a temporary schema
+ * (or temporary toast schema), or if either the old or new namespaces is the
+ * TOAST schema.
*/
void
-CheckSetNamespace(Oid oldNspOid, Oid nspOid, Oid classid, Oid objid)
+CheckSetNamespace(Oid oldNspOid, Oid nspOid)
{
- if (oldNspOid == nspOid)
- ereport(ERROR,
- (classid == RelationRelationId ?
- errcode(ERRCODE_DUPLICATE_TABLE) :
- classid == ProcedureRelationId ?
- errcode(ERRCODE_DUPLICATE_FUNCTION) :
- errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("%s is already in schema \"%s\"",
- getObjectDescriptionOids(classid, objid),
- get_namespace_name(nspOid))));
-
/* disallow renaming into or out of temp schemas */
if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
ereport(ERROR,
/*
* get_namespace_oid - given a namespace name, look up the OID
*
- * If missing_ok is false, throw an error if namespace name not found. If
+ * If missing_ok is false, throw an error if namespace name not found. If
* true, just return InvalidOid.
*/
Oid
if (IsA(name, String))
appendStringInfoString(&string, strVal(name));
else if (IsA(name, A_Star))
- appendStringInfoString(&string, "*");
+ appendStringInfoChar(&string, '*');
else
elog(ERROR, "unexpected node type in name list: %d",
(int) nodeTag(name));
}
/*
- * isTempOrToastNamespace - is the given namespace my temporary-table
+ * isTempOrTempToastNamespace - is the given namespace my temporary-table
* namespace or my temporary-toast-table namespace?
*/
bool
-isTempOrToastNamespace(Oid namespaceId)
+isTempOrTempToastNamespace(Oid namespaceId)
{
if (OidIsValid(myTempNamespace) &&
(myTempNamespace == namespaceId || myTempToastNamespace == namespaceId))
isOtherTempNamespace(Oid namespaceId)
{
/* If it's my own temp namespace, say "false" */
- if (isTempOrToastNamespace(namespaceId))
+ if (isTempOrTempToastNamespace(namespaceId))
return false;
/* Else, if it's any temp namespace, say "true" */
return isAnyTempNamespace(namespaceId);
/*
* GetTempToastNamespace - get the OID of my temporary-toast-table namespace,
- * which must already be assigned. (This is only used when creating a toast
+ * which must already be assigned. (This is only used when creating a toast
* table for a temp table, so we must have already done InitTempTableNamespace)
*/
Oid
return result;
}
+/*
+ * OverrideSearchPathMatchesCurrent - does path match current setting?
+ */
+bool
+OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
+{
+ ListCell *lc,
+ *lcp;
+
+ recomputeNamespacePath();
+
+ /* We scan down the activeSearchPath to see if it matches the input. */
+ lc = list_head(activeSearchPath);
+
+ /* If path->addTemp, first item should be my temp namespace. */
+ if (path->addTemp)
+ {
+ if (lc && lfirst_oid(lc) == myTempNamespace)
+ lc = lnext(lc);
+ else
+ return false;
+ }
+ /* If path->addCatalog, next item should be pg_catalog. */
+ if (path->addCatalog)
+ {
+ if (lc && lfirst_oid(lc) == PG_CATALOG_NAMESPACE)
+ lc = lnext(lc);
+ else
+ return false;
+ }
+ /* We should now be looking at the activeCreationNamespace. */
+ if (activeCreationNamespace != (lc ? lfirst_oid(lc) : InvalidOid))
+ return false;
+ /* The remainder of activeSearchPath should match path->schemas. */
+ foreach(lcp, path->schemas)
+ {
+ if (lc && lfirst_oid(lc) == lfirst_oid(lcp))
+ lc = lnext(lc);
+ else
+ return false;
+ }
+ if (lc)
+ return false;
+ return true;
+}
+
/*
* PushOverrideSearchPath - temporarily override the search path
*
*
* It's possible that newpath->useTemp is set but there is no longer any
* active temp namespace, if the path was saved during a transaction that
- * created a temp namespace and was later rolled back. In that case we just
- * ignore useTemp. A plausible alternative would be to create a new temp
+ * created a temp namespace and was later rolled back. In that case we just
+ * ignore useTemp. A plausible alternative would be to create a new temp
* namespace, but for existing callers that's not necessary because an empty
* temp namespace wouldn't affect their results anyway.
*
firstNS = linitial_oid(oidlist);
/*
- * Add any implicitly-searched namespaces to the list. Note these go on
+ * Add any implicitly-searched namespaces to the list. Note these go on
* the front, not the back; also notice that we do not check USAGE
* permissions for these.
*/
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ return InvalidOid;
/* first try for encoding-specific entry, then any-encoding */
colloid = GetSysCacheOid3(COLLNAMEENCNSP,
if (schemaname)
{
/* use exact schema given */
- namespaceId = LookupExplicitNamespace(schemaname);
- conoid = GetSysCacheOid2(CONNAMENSP,
- PointerGetDatum(conversion_name),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ conoid = InvalidOid;
+ else
+ conoid = GetSysCacheOid2(CONNAMENSP,
+ PointerGetDatum(conversion_name),
+ ObjectIdGetDatum(namespaceId));
}
else
{
* FindDefaultConversionProc - find default encoding conversion proc
*/
Oid
-FindDefaultConversionProc(int4 for_encoding, int4 to_encoding)
+FindDefaultConversionProc(int32 for_encoding, int32 to_encoding)
{
Oid proc;
ListCell *l;
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
pg_namespace_aclcheck(namespaceId, roleid,
- ACL_USAGE) == ACLCHECK_OK)
+ ACL_USAGE) == ACLCHECK_OK &&
+ InvokeNamespaceSearchHook(namespaceId, false))
oidlist = lappend_oid(oidlist, namespaceId);
}
}
/* pg_temp --- substitute temp namespace, if any */
if (OidIsValid(myTempNamespace))
{
- if (!list_member_oid(oidlist, myTempNamespace))
+ if (!list_member_oid(oidlist, myTempNamespace) &&
+ InvokeNamespaceSearchHook(myTempNamespace, false))
oidlist = lappend_oid(oidlist, myTempNamespace);
}
else
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
pg_namespace_aclcheck(namespaceId, roleid,
- ACL_USAGE) == ACLCHECK_OK)
+ ACL_USAGE) == ACLCHECK_OK &&
+ InvokeNamespaceSearchHook(namespaceId, false))
oidlist = lappend_oid(oidlist, namespaceId);
}
}
/*
- * Remember the first member of the explicit list. (Note: this is
+ * Remember the first member of the explicit list. (Note: this is
* nominally wrong if temp_missing, but we need it anyway to distinguish
* explicit from implicit mention of pg_catalog.)
*/
firstNS = linitial_oid(oidlist);
/*
- * Add any implicitly-searched namespaces to the list. Note these go on
+ * Add any implicitly-searched namespaces to the list. Note these go on
* the front, not the back; also notice that we do not check USAGE
* permissions for these.
*/
/*
* First, do permission check to see if we are authorized to make temp
- * tables. We use a nonstandard error message here since "databasename:
+ * tables. We use a nonstandard error message here since "databasename:
* permission denied" might be a tad cryptic.
*
* Note that ACL_CREATE_TEMP rights are rechecked in pg_namespace_aclmask;
* Do not allow a Hot Standby slave session to make temp tables. Aside
* from problems with modifying the system catalogs, there is a naming
* conflict: pg_temp_N belongs to the session with BackendId N on the
- * master, not to a slave session with the same BackendId. We should not
+ * master, not to a slave session with the same BackendId. We should not
* be able to get here anyway due to XactReadOnly checks, but let's just
- * make real sure. Note that this also backstops various operations that
+ * make real sure. Note that this also backstops various operations that
* allow XactReadOnly transactions to modify temp tables; they'd need
* RecoveryInProgress checks if not for this.
*/
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("cannot create temporary tables during recovery")));
+ /* Parallel workers can't create temporary tables, either. */
+ if (IsParallelWorker())
+ ereport(ERROR,
+ (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+ errmsg("cannot create temporary tables in parallel mode")));
+
snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
namespaceId = get_namespace_oid(namespaceName, true);
* temp tables. This works because the places that access the temp
* namespace for my own backend skip permissions checks on it.
*/
- namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID);
+ namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
+ true);
/* Advance command counter to make namespace visible */
CommandCounterIncrement();
}
toastspaceId = get_namespace_oid(namespaceName, true);
if (!OidIsValid(toastspaceId))
{
- toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID);
+ toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
+ true);
/* Advance command counter to make namespace visible */
CommandCounterIncrement();
}
* End-of-transaction cleanup for namespaces.
*/
void
-AtEOXact_Namespace(bool isCommit)
+AtEOXact_Namespace(bool isCommit, bool parallel)
{
/*
* If we abort the transaction in which a temp namespace was selected,
* at backend shutdown. (We only want to register the callback once per
* session, so this is a good place to do it.)
*/
- if (myTempNamespaceSubID != InvalidSubTransactionId)
+ if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel)
{
if (isCommit)
- on_shmem_exit(RemoveTempRelationsCallback, 0);
+ before_shmem_exit(RemoveTempRelationsCallback, 0);
else
{
myTempNamespace = InvalidOid;
* Routines for handling the GUC variable 'search_path'.
*/
-/* check_hook: validate new search_path, if possible */
+/* check_hook: validate new search_path value */
bool
check_search_path(char **newval, void **extra, GucSource source)
{
- bool result = true;
char *rawname;
List *namelist;
- ListCell *l;
/* Need a modifiable copy of string */
rawname = pstrdup(*newval);
}
/*
- * If we aren't inside a transaction, we cannot do database access so
- * cannot verify the individual names. Must accept the list on faith.
+ * We used to try to check that the named schemas exist, but there are
+ * many valid use-cases for having search_path settings that include
+ * schemas that don't exist; and often, we are not inside a transaction
+ * here and so can't consult the system catalogs anyway. So now, the only
+ * requirement is syntactic validity of the identifier list.
*/
- if (IsTransactionState())
- {
- /*
- * Verify that all the names are either valid namespace names or
- * "$user" or "pg_temp". We do not require $user to correspond to a
- * valid namespace, and pg_temp might not exist yet. We do not check
- * for USAGE rights, either; should we?
- *
- * When source == PGC_S_TEST, we are checking the argument of an ALTER
- * DATABASE SET or ALTER USER SET command. It could be that the
- * intended use of the search path is for some other database, so we
- * should not error out if it mentions schemas not present in the
- * current database. We issue a NOTICE instead.
- */
- foreach(l, namelist)
- {
- char *curname = (char *) lfirst(l);
-
- if (strcmp(curname, "$user") == 0)
- continue;
- if (strcmp(curname, "pg_temp") == 0)
- continue;
- if (!SearchSysCacheExists1(NAMESPACENAME,
- CStringGetDatum(curname)))
- {
- if (source == PGC_S_TEST)
- ereport(NOTICE,
- (errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("schema \"%s\" does not exist", curname)));
- else
- {
- GUC_check_errdetail("schema \"%s\" does not exist", curname);
- result = false;
- break;
- }
- }
- }
- }
pfree(rawname);
list_free(namelist);
- return result;
+ return true;
}
/* assign_hook: do extra actions as needed */
/*
* If the temp namespace should be first, force it to exist. This is so
* that callers can trust the result to reflect the actual default
- * creation namespace. It's a bit bogus to do this here, since
+ * creation namespace. It's a bit bogus to do this here, since
* current_schema() is supposedly a stable function without side-effects,
* but the alternatives seem worse.
*/
/*
* Fetch the active search path into a caller-allocated array of OIDs.
- * Returns the number of path entries. (If this is more than sarray_len,
+ * Returns the number of path entries. (If this is more than sarray_len,
* then the data didn't fit and is not all stored.)
*
* The returned list always includes the implicitly-prepended namespaces,
* a nonexistent object OID, rather than failing. This is to avoid race
* condition errors when a query that's scanning a catalog using an MVCC
* snapshot uses one of these functions. The underlying IsVisible functions
- * operate on SnapshotNow semantics and so might see the object as already
- * gone when it's still visible to the MVCC snapshot. (There is no race
+ * always use an up-to-date snapshot and so might see the object as already
+ * gone when it's still visible to the transaction snapshot. (There is no race
* condition in the current coding because we don't accept sinval messages
* between the SearchSysCacheExists test and the subsequent lookup.)
*/