postgresql/src/backend/catalog/namespace.c

2049 lines
54 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* namespace.c
* code to support accessing and searching namespaces
*
* This is separate from pg_namespace.c, which contains the routines that
* directly manipulate the pg_namespace system catalog. This module
* provides routines associated with defining a "namespace search path"
* and implementing search-path-controlled searches.
*
*
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.77 2005/08/01 04:03:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
2002-07-16 08:58:14 +02:00
#include "catalog/pg_conversion.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
2003-06-27 19:07:03 +02:00
#include "commands/dbcommands.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "storage/backendid.h"
#include "storage/ipc.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"
#include "utils/memutils.h"
#include "utils/syscache.h"
/*
* The namespace search path is a possibly-empty list of namespace OIDs.
* In addition to the explicit list, several implicitly-searched namespaces
* may be included:
*
* 1. If a "special" namespace has been set by PushSpecialNamespace, it is
* always searched first. (This is a hack for CREATE SCHEMA.)
*
* 2. If a TEMP table namespace has been initialized in this session, it
* is always searched just after any special namespace.
*
2002-09-04 22:31:48 +02:00
* 3. 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
2002-09-04 22:31:48 +02:00
* *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.)
*
* The default creation target namespace is normally equal to the first
* element of the explicit list, but is the "special" namespace when one
* has been set. If the explicit list is empty and there is no special
* namespace, there is no default target.
*
* 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.
* The initdb script is also careful to set search_path to 'pg_catalog' for
2002-09-04 22:31:48 +02:00
* its 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).
*
* If namespaceSearchPathValid is false, then namespaceSearchPath (and other
* derived variables) need to be recomputed from namespace_search_path.
* We mark it invalid upon an assignment to namespace_search_path or receipt
* of a syscache invalidation event for pg_namespace. The recomputation
* is done during the next lookup attempt.
*
* Any namespaces mentioned in namespace_search_path that are not readable
* by the current user ID are simply left out of namespaceSearchPath; so
* we have to be willing to recompute the path when current userid changes.
* namespaceUser is the userid the path has been computed for.
*/
static List *namespaceSearchPath = NIL;
static Oid namespaceUser = InvalidOid;
/* default place to create stuff; if InvalidOid, no default */
static Oid defaultCreationNamespace = InvalidOid;
/* first explicit member of list; usually same as defaultCreationNamespace */
static Oid firstExplicitNamespace = InvalidOid;
/* The above four values are valid only if namespaceSearchPathValid */
static bool namespaceSearchPathValid = true;
/*
* myTempNamespace is InvalidOid until and unless a TEMP namespace is set up
* in a particular backend session (this happens when a CREATE TEMP TABLE
2002-09-04 22:31:48 +02:00
* command is first executed). Thereafter it's the OID of the temp namespace.
*
* myTempNamespaceSubID shows whether we've created the TEMP namespace in the
* 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
* committed its creation, depending on whether myTempNamespace is valid.
*/
static Oid myTempNamespace = InvalidOid;
static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId;
/*
* "Special" namespace for CREATE SCHEMA. If set, it's the first search
* path element, and also the default creation namespace.
*/
static Oid mySpecialNamespace = InvalidOid;
/*
* This is the text equivalent of the search path --- it's the value
* of the GUC variable 'search_path'.
*/
2002-09-04 22:31:48 +02:00
char *namespace_search_path = NULL;
/* Local functions */
static void recomputeNamespacePath(void);
static void InitTempTableNamespace(void);
static void RemoveTempRelations(Oid tempNamespaceId);
static void RemoveTempRelationsCallback(int code, Datum arg);
static void NamespaceCallback(Datum arg, Oid relid);
/* These don't really need to appear in any header file */
2002-09-04 22:31:48 +02:00
Datum pg_table_is_visible(PG_FUNCTION_ARGS);
Datum pg_type_is_visible(PG_FUNCTION_ARGS);
Datum pg_function_is_visible(PG_FUNCTION_ARGS);
Datum pg_operator_is_visible(PG_FUNCTION_ARGS);
Datum pg_opclass_is_visible(PG_FUNCTION_ARGS);
Datum pg_conversion_is_visible(PG_FUNCTION_ARGS);
/*
* RangeVarGetRelid
* 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 failOK = true,
* otherwise raise an error.
*/
Oid
RangeVarGetRelid(const RangeVar *relation, bool failOK)
{
Oid namespaceId;
Oid relId;
/*
* We check the catalog name and then ignore it.
*/
if (relation->catalogname)
{
2003-06-27 16:45:32 +02:00
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2004-08-29 07:07:03 +02:00
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
relation->relname)));
}
if (relation->schemaname)
{
/* use exact schema given */
namespaceId = LookupExplicitNamespace(relation->schemaname);
relId = get_relname_relid(relation->relname, namespaceId);
}
else
{
/* search the namespace path */
relId = RelnameGetRelid(relation->relname);
}
if (!OidIsValid(relId) && !failOK)
{
if (relation->schemaname)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
}
return relId;
}
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
* choose which namespace to create it in.
*
* Note: calling this may result in a CommandCounterIncrement operation.
* That will happen on the first request for a temp table in any particular
* backend run; we will need to either create or clean out the temp schema.
*/
Oid
RangeVarGetCreationNamespace(const RangeVar *newRelation)
{
Oid namespaceId;
/*
* We check the catalog name and then ignore it.
*/
if (newRelation->catalogname)
{
2003-06-27 16:45:32 +02:00
if (strcmp(newRelation->catalogname, get_database_name(MyDatabaseId)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2004-08-29 07:07:03 +02:00
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
newRelation->catalogname, newRelation->schemaname,
newRelation->relname)));
}
if (newRelation->istemp)
{
/* TEMP tables are created in our backend-local temp namespace */
if (newRelation->schemaname)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
2004-08-29 07:07:03 +02:00
errmsg("temporary tables may not specify a schema name")));
/* Initialize temp namespace if first time through */
if (!OidIsValid(myTempNamespace))
InitTempTableNamespace();
return myTempNamespace;
}
if (newRelation->schemaname)
{
/* use exact schema given */
namespaceId = GetSysCacheOid(NAMESPACENAME,
2002-09-04 22:31:48 +02:00
CStringGetDatum(newRelation->schemaname),
0, 0, 0);
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema \"%s\" does not exist",
newRelation->schemaname)));
/* we do not check for USAGE rights here! */
}
else
{
/* use the default creation namespace */
recomputeNamespacePath();
namespaceId = defaultCreationNamespace;
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("no schema has been selected to create in")));
}
/* Note: callers will check for CREATE rights when appropriate */
return namespaceId;
}
/*
* RelnameGetRelid
* Try to resolve an unqualified relation name.
* Returns OID if relation found in search path, else InvalidOid.
*/
Oid
RelnameGetRelid(const char *relname)
{
Oid relid;
ListCell *l;
recomputeNamespacePath();
foreach(l, namespaceSearchPath)
{
Oid namespaceId = lfirst_oid(l);
relid = get_relname_relid(relname, namespaceId);
if (OidIsValid(relid))
return relid;
}
/* Not found in path */
return InvalidOid;
}
/*
* RelationIsVisible
* Determine whether a relation (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified relation name".
*/
bool
RelationIsVisible(Oid relid)
{
HeapTuple reltup;
Form_pg_class relform;
Oid relnamespace;
bool visible;
reltup = SearchSysCache(RELOID,
ObjectIdGetDatum(relid),
0, 0, 0);
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", relid);
relform = (Form_pg_class) GETSTRUCT(reltup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
relnamespace = relform->relnamespace;
if (relnamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, relnamespace))
visible = false;
else
{
/*
2002-09-04 22:31:48 +02:00
* If it is in the path, it might still not be visible; it could
* be hidden by another relation of the same name earlier in the
* path. So we must do a slow check to see if this rel would be
* found by RelnameGetRelid.
*/
char *relname = NameStr(relform->relname);
visible = (RelnameGetRelid(relname) == relid);
}
ReleaseSysCache(reltup);
return visible;
}
/*
* TypenameGetTypid
* Try to resolve an unqualified datatype name.
* Returns OID if type found in search path, else InvalidOid.
*
* This is essentially the same as RelnameGetRelid.
*/
Oid
TypenameGetTypid(const char *typname)
{
Oid typid;
ListCell *l;
recomputeNamespacePath();
foreach(l, namespaceSearchPath)
{
Oid namespaceId = lfirst_oid(l);
typid = GetSysCacheOid(TYPENAMENSP,
PointerGetDatum(typname),
ObjectIdGetDatum(namespaceId),
0, 0);
if (OidIsValid(typid))
return typid;
}
/* Not found in path */
return InvalidOid;
}
/*
* TypeIsVisible
* Determine whether a type (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified type name".
*/
bool
TypeIsVisible(Oid typid)
{
HeapTuple typtup;
Form_pg_type typform;
Oid typnamespace;
bool visible;
typtup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(typid),
0, 0, 0);
if (!HeapTupleIsValid(typtup))
elog(ERROR, "cache lookup failed for type %u", typid);
typform = (Form_pg_type) GETSTRUCT(typtup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
typnamespace = typform->typnamespace;
if (typnamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, typnamespace))
visible = false;
else
{
/*
2002-09-04 22:31:48 +02:00
* If it is in the path, it might still not be visible; it could
* be hidden by another type of the same name earlier in the path.
* So we must do a slow check to see if this type would be found
* by TypenameGetTypid.
*/
char *typname = NameStr(typform->typname);
visible = (TypenameGetTypid(typname) == typid);
}
ReleaseSysCache(typtup);
return visible;
}
/*
* FuncnameGetCandidates
* Given a possibly-qualified function name and argument count,
* retrieve a list of the possible matches.
*
* If nargs is -1, we return all functions matching the given name,
* regardless of argument count.
*
* We search a single namespace if the function name is qualified, else
* all namespaces in the search path. The return list will never contain
* multiple entries with identical argument lists --- in the multiple-
* namespace case, we arrange for entries in earlier namespaces to mask
* identical entries in later namespaces.
*/
FuncCandidateList
FuncnameGetCandidates(List *names, int nargs)
{
FuncCandidateList resultList = NULL;
char *schemaname;
char *funcname;
Oid namespaceId;
CatCList *catlist;
int i;
/* deconstruct the name list */
DeconstructQualifiedName(names, &schemaname, &funcname);
if (schemaname)
{
/* use exact schema given */
namespaceId = LookupExplicitNamespace(schemaname);
}
else
{
/* flag to indicate we need namespace search */
namespaceId = InvalidOid;
recomputeNamespacePath();
}
/* Search syscache by name only */
catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1,
CStringGetDatum(funcname),
0, 0, 0);
for (i = 0; i < catlist->n_members; i++)
{
HeapTuple proctup = &catlist->members[i]->tuple;
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
int pronargs = procform->pronargs;
int pathpos = 0;
FuncCandidateList newResult;
/* Ignore if it doesn't match requested argument count */
if (nargs >= 0 && pronargs != nargs)
continue;
if (OidIsValid(namespaceId))
{
/* Consider only procs in specified namespace */
if (procform->pronamespace != namespaceId)
continue;
/* No need to check args, they must all be different */
}
else
{
/* Consider only procs that are in the search path */
ListCell *nsp;
foreach(nsp, namespaceSearchPath)
{
if (procform->pronamespace == lfirst_oid(nsp))
break;
pathpos++;
}
if (nsp == NULL)
continue; /* proc is not in search path */
/*
* Okay, it's in the search path, but does it have the same
2002-09-04 22:31:48 +02:00
* arguments as something we already accepted? If so, keep
* only the one that appears earlier in the search path.
*
2002-09-04 22:31:48 +02:00
* If we have an ordered list from SearchSysCacheList (the normal
* case), then any conflicting proc must immediately adjoin
* this one in the list, so we only need to look at the newest
* result item. If we have an unordered list, we have to scan
* the whole result list.
*/
if (resultList)
{
2002-09-04 22:31:48 +02:00
FuncCandidateList prevResult;
if (catlist->ordered)
{
if (pronargs == resultList->nargs &&
memcmp(procform->proargtypes.values,
resultList->args,
pronargs * sizeof(Oid)) == 0)
prevResult = resultList;
else
prevResult = NULL;
}
else
{
for (prevResult = resultList;
prevResult;
prevResult = prevResult->next)
{
if (pronargs == prevResult->nargs &&
memcmp(procform->proargtypes.values,
prevResult->args,
pronargs * sizeof(Oid)) == 0)
break;
}
}
if (prevResult)
{
/* We have a match with a previous result */
Assert(pathpos != prevResult->pathpos);
if (pathpos > prevResult->pathpos)
2002-09-04 22:31:48 +02:00
continue; /* keep previous result */
/* replace previous result */
prevResult->pathpos = pathpos;
prevResult->oid = HeapTupleGetOid(proctup);
continue; /* args are same, of course */
}
}
}
/*
* Okay to add it to result list
*/
newResult = (FuncCandidateList)
palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
+ pronargs * sizeof(Oid));
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(proctup);
newResult->nargs = pronargs;
memcpy(newResult->args, procform->proargtypes.values,
pronargs * sizeof(Oid));
newResult->next = resultList;
resultList = newResult;
}
ReleaseSysCacheList(catlist);
return resultList;
}
/*
* FunctionIsVisible
* Determine whether a function (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified function name with exact argument matches".
*/
bool
FunctionIsVisible(Oid funcid)
{
HeapTuple proctup;
Form_pg_proc procform;
Oid pronamespace;
bool visible;
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
pronamespace = procform->pronamespace;
if (pronamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, pronamespace))
visible = false;
else
{
/*
2002-09-04 22:31:48 +02:00
* If it is in the path, it might still not be visible; it could
* be hidden by another proc of the same name and arguments
* earlier in the path. So we must do a slow check to see if this
* is the same proc that would be found by FuncnameGetCandidates.
*/
char *proname = NameStr(procform->proname);
int nargs = procform->pronargs;
FuncCandidateList clist;
visible = false;
clist = FuncnameGetCandidates(list_make1(makeString(proname)), nargs);
for (; clist; clist = clist->next)
{
if (memcmp(clist->args, procform->proargtypes.values,
nargs * sizeof(Oid)) == 0)
{
/* Found the expected entry; is it the right proc? */
visible = (clist->oid == funcid);
break;
}
}
}
ReleaseSysCache(proctup);
return visible;
}
/*
* OpernameGetCandidates
* Given a possibly-qualified operator name and operator kind,
* retrieve a list of the possible matches.
*
* If oprkind is '\0', we return all operators matching the given name,
* regardless of arguments.
*
* We search a single namespace if the operator name is qualified, else
* all namespaces in the search path. The return list will never contain
* multiple entries with identical argument lists --- in the multiple-
* namespace case, we arrange for entries in earlier namespaces to mask
* identical entries in later namespaces.
*
* The returned items always have two args[] entries --- one or the other
2002-09-04 22:31:48 +02:00
* will be InvalidOid for a prefix or postfix oprkind. nargs is 2, too.
*/
FuncCandidateList
OpernameGetCandidates(List *names, char oprkind)
{
FuncCandidateList resultList = NULL;
char *resultSpace = NULL;
int nextResult = 0;
char *schemaname;
char *opername;
Oid namespaceId;
CatCList *catlist;
int i;
/* deconstruct the name list */
DeconstructQualifiedName(names, &schemaname, &opername);
if (schemaname)
{
/* use exact schema given */
namespaceId = LookupExplicitNamespace(schemaname);
}
else
{
/* flag to indicate we need namespace search */
namespaceId = InvalidOid;
recomputeNamespacePath();
}
/* Search syscache by name only */
catlist = SearchSysCacheList(OPERNAMENSP, 1,
CStringGetDatum(opername),
0, 0, 0);
/*
* In typical scenarios, most if not all of the operators found by the
2004-08-29 07:07:03 +02:00
* catcache search will end up getting returned; and there can be
* quite a few, for common operator names such as '=' or '+'. To
* reduce the time spent in palloc, we allocate the result space as an
* array large enough to hold all the operators. The original coding
* of this routine did a 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))
if (catlist->n_members > 0)
resultSpace = palloc(catlist->n_members * SPACE_PER_OP);
for (i = 0; i < catlist->n_members; i++)
{
HeapTuple opertup = &catlist->members[i]->tuple;
Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup);
int pathpos = 0;
FuncCandidateList newResult;
/* Ignore operators of wrong kind, if specific kind requested */
if (oprkind && operform->oprkind != oprkind)
continue;
if (OidIsValid(namespaceId))
{
/* Consider only opers in specified namespace */
if (operform->oprnamespace != namespaceId)
continue;
/* No need to check args, they must all be different */
}
else
{
/* Consider only opers that are in the search path */
ListCell *nsp;
foreach(nsp, namespaceSearchPath)
{
if (operform->oprnamespace == lfirst_oid(nsp))
break;
pathpos++;
}
if (nsp == NULL)
continue; /* oper is not in search path */
/*
* Okay, it's in the search path, but does it have the same
2002-09-04 22:31:48 +02:00
* arguments as something we already accepted? If so, keep
* only the one that appears earlier in the search path.
*
2002-09-04 22:31:48 +02:00
* If we have an ordered list from SearchSysCacheList (the normal
* case), then any conflicting oper must immediately adjoin
* this one in the list, so we only need to look at the newest
* result item. If we have an unordered list, we have to scan
* the whole result list.
*/
if (resultList)
{
2002-09-04 22:31:48 +02:00
FuncCandidateList prevResult;
if (catlist->ordered)
{
if (operform->oprleft == resultList->args[0] &&
operform->oprright == resultList->args[1])
prevResult = resultList;
else
prevResult = NULL;
}
else
{
for (prevResult = resultList;
prevResult;
prevResult = prevResult->next)
{
if (operform->oprleft == prevResult->args[0] &&
operform->oprright == prevResult->args[1])
break;
}
}
if (prevResult)
{
/* We have a match with a previous result */
Assert(pathpos != prevResult->pathpos);
if (pathpos > prevResult->pathpos)
2002-09-04 22:31:48 +02:00
continue; /* keep previous result */
/* replace previous result */
prevResult->pathpos = pathpos;
prevResult->oid = HeapTupleGetOid(opertup);
continue; /* args are same, of course */
}
}
}
/*
* Okay to add it to result list
*/
newResult = (FuncCandidateList) (resultSpace + nextResult);
nextResult += SPACE_PER_OP;
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(opertup);
newResult->nargs = 2;
newResult->args[0] = operform->oprleft;
newResult->args[1] = operform->oprright;
newResult->next = resultList;
resultList = newResult;
}
ReleaseSysCacheList(catlist);
return resultList;
}
/*
* OperatorIsVisible
* Determine whether an operator (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified operator name with exact argument matches".
*/
bool
OperatorIsVisible(Oid oprid)
{
HeapTuple oprtup;
Form_pg_operator oprform;
Oid oprnamespace;
bool visible;
oprtup = SearchSysCache(OPEROID,
ObjectIdGetDatum(oprid),
0, 0, 0);
if (!HeapTupleIsValid(oprtup))
elog(ERROR, "cache lookup failed for operator %u", oprid);
oprform = (Form_pg_operator) GETSTRUCT(oprtup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
oprnamespace = oprform->oprnamespace;
if (oprnamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, oprnamespace))
visible = false;
else
{
/*
2002-09-04 22:31:48 +02:00
* 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
* OpernameGetCandidates.
*/
char *oprname = NameStr(oprform->oprname);
FuncCandidateList clist;
visible = false;
clist = OpernameGetCandidates(list_make1(makeString(oprname)),
oprform->oprkind);
for (; clist; clist = clist->next)
{
if (clist->args[0] == oprform->oprleft &&
clist->args[1] == oprform->oprright)
{
/* Found the expected entry; is it the right op? */
visible = (clist->oid == oprid);
break;
}
}
}
ReleaseSysCache(oprtup);
return visible;
}
/*
* OpclassGetCandidates
* Given an index access method OID, retrieve a list of all the
* opclasses for that AM that are visible in the search path.
*
* NOTE: the opcname_tmp field in the returned structs should not be used
* by callers, because it points at syscache entries that we release at
* the end of this routine. If any callers needed the name information,
* we could pstrdup() the names ... but at present it'd be wasteful.
*/
OpclassCandidateList
OpclassGetCandidates(Oid amid)
{
OpclassCandidateList resultList = NULL;
CatCList *catlist;
int i;
/* Search syscache by AM OID only */
catlist = SearchSysCacheList(CLAAMNAMENSP, 1,
ObjectIdGetDatum(amid),
0, 0, 0);
recomputeNamespacePath();
for (i = 0; i < catlist->n_members; i++)
{
HeapTuple opctup = &catlist->members[i]->tuple;
Form_pg_opclass opcform = (Form_pg_opclass) GETSTRUCT(opctup);
int pathpos = 0;
OpclassCandidateList newResult;
ListCell *nsp;
/* Consider only opclasses that are in the search path */
foreach(nsp, namespaceSearchPath)
{
if (opcform->opcnamespace == lfirst_oid(nsp))
break;
pathpos++;
}
if (nsp == NULL)
continue; /* opclass is not in search path */
/*
* Okay, it's in the search path, but does it have the same name
2002-09-04 22:31:48 +02:00
* as something we already accepted? If so, keep only the one
* that appears earlier in the search path.
*
2002-09-04 22:31:48 +02:00
* If we have an ordered list from SearchSysCacheList (the normal
* case), then any conflicting opclass must immediately adjoin
* this one in the list, so we only need to look at the newest
* result item. If we have an unordered list, we have to scan the
* whole result list.
*/
if (resultList)
{
2002-09-04 22:31:48 +02:00
OpclassCandidateList prevResult;
if (catlist->ordered)
{
if (strcmp(NameStr(opcform->opcname),
resultList->opcname_tmp) == 0)
prevResult = resultList;
else
prevResult = NULL;
}
else
{
for (prevResult = resultList;
prevResult;
prevResult = prevResult->next)
{
if (strcmp(NameStr(opcform->opcname),
prevResult->opcname_tmp) == 0)
break;
}
}
if (prevResult)
{
/* We have a match with a previous result */
Assert(pathpos != prevResult->pathpos);
if (pathpos > prevResult->pathpos)
2002-09-04 22:31:48 +02:00
continue; /* keep previous result */
/* replace previous result */
prevResult->opcname_tmp = NameStr(opcform->opcname);
prevResult->pathpos = pathpos;
prevResult->oid = HeapTupleGetOid(opctup);
prevResult->opcintype = opcform->opcintype;
prevResult->opcdefault = opcform->opcdefault;
prevResult->opckeytype = opcform->opckeytype;
continue;
}
}
/*
* Okay to add it to result list
*/
newResult = (OpclassCandidateList)
palloc(sizeof(struct _OpclassCandidateList));
newResult->opcname_tmp = NameStr(opcform->opcname);
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(opctup);
newResult->opcintype = opcform->opcintype;
newResult->opcdefault = opcform->opcdefault;
newResult->opckeytype = opcform->opckeytype;
newResult->next = resultList;
resultList = newResult;
}
ReleaseSysCacheList(catlist);
return resultList;
}
/*
* OpclassnameGetOpcid
* Try to resolve an unqualified index opclass name.
* Returns OID if opclass found in search path, else InvalidOid.
*
* This is essentially the same as TypenameGetTypid, but we have to have
* an extra argument for the index AM OID.
*/
Oid
OpclassnameGetOpcid(Oid amid, const char *opcname)
{
Oid opcid;
ListCell *l;
recomputeNamespacePath();
foreach(l, namespaceSearchPath)
{
Oid namespaceId = lfirst_oid(l);
opcid = GetSysCacheOid(CLAAMNAMENSP,
ObjectIdGetDatum(amid),
PointerGetDatum(opcname),
ObjectIdGetDatum(namespaceId),
0);
if (OidIsValid(opcid))
return opcid;
}
/* Not found in path */
return InvalidOid;
}
/*
* OpclassIsVisible
* Determine whether an opclass (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified opclass name".
*/
bool
OpclassIsVisible(Oid opcid)
{
HeapTuple opctup;
Form_pg_opclass opcform;
Oid opcnamespace;
bool visible;
opctup = SearchSysCache(CLAOID,
ObjectIdGetDatum(opcid),
0, 0, 0);
if (!HeapTupleIsValid(opctup))
elog(ERROR, "cache lookup failed for opclass %u", opcid);
opcform = (Form_pg_opclass) GETSTRUCT(opctup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
opcnamespace = opcform->opcnamespace;
if (opcnamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, opcnamespace))
visible = false;
else
{
/*
2002-09-04 22:31:48 +02:00
* If it is in the path, it might still not be visible; it could
* be hidden by another opclass of the same name earlier in the
* path. So we must do a slow check to see if this opclass would
* be found by OpclassnameGetOpcid.
*/
char *opcname = NameStr(opcform->opcname);
visible = (OpclassnameGetOpcid(opcform->opcamid, opcname) == opcid);
}
ReleaseSysCache(opctup);
return visible;
}
/*
* ConversionGetConid
* Try to resolve an unqualified conversion name.
* Returns OID if conversion found in search path, else InvalidOid.
*
* This is essentially the same as RelnameGetRelid.
*/
Oid
ConversionGetConid(const char *conname)
{
2003-08-04 02:43:34 +02:00
Oid conid;
ListCell *l;
recomputeNamespacePath();
foreach(l, namespaceSearchPath)
{
Oid namespaceId = lfirst_oid(l);
conid = GetSysCacheOid(CONNAMENSP,
PointerGetDatum(conname),
ObjectIdGetDatum(namespaceId),
0, 0);
if (OidIsValid(conid))
return conid;
}
/* Not found in path */
return InvalidOid;
}
/*
* ConversionIsVisible
* Determine whether a conversion (identified by OID) is visible in the
* current search path. Visible means "would be found by searching
* for the unqualified conversion name".
*/
bool
ConversionIsVisible(Oid conid)
{
HeapTuple contup;
Form_pg_conversion conform;
Oid connamespace;
bool visible;
contup = SearchSysCache(CONOID,
ObjectIdGetDatum(conid),
0, 0, 0);
if (!HeapTupleIsValid(contup))
elog(ERROR, "cache lookup failed for conversion %u", conid);
conform = (Form_pg_conversion) GETSTRUCT(contup);
recomputeNamespacePath();
/*
* Quick check: if it ain't in the path at all, it ain't visible.
* Items in the system namespace are surely in the path and so we
* needn't even do list_member_oid() for them.
*/
connamespace = conform->connamespace;
if (connamespace != PG_CATALOG_NAMESPACE &&
!list_member_oid(namespaceSearchPath, connamespace))
visible = false;
else
{
/*
* If it is in the path, it might still not be visible; it could
* be hidden by another conversion of the same name earlier in the
2003-08-04 02:43:34 +02:00
* path. So we must do a slow check to see if this conversion
* would be found by ConversionGetConid.
*/
char *conname = NameStr(conform->conname);
2003-08-04 02:43:34 +02:00
visible = (ConversionGetConid(conname) == conid);
}
ReleaseSysCache(contup);
return visible;
}
/*
* DeconstructQualifiedName
* Given a possibly-qualified name expressed as a list of String nodes,
* extract the schema name and object name.
*
* *nspname_p is set to NULL if there is no explicit schema name.
*/
void
DeconstructQualifiedName(List *names,
char **nspname_p,
char **objname_p)
{
char *catalogname;
char *schemaname = NULL;
char *objname = NULL;
switch (list_length(names))
{
case 1:
objname = strVal(linitial(names));
break;
case 2:
schemaname = strVal(linitial(names));
objname = strVal(lsecond(names));
break;
case 3:
catalogname = strVal(linitial(names));
schemaname = strVal(lsecond(names));
objname = strVal(lthird(names));
2002-09-04 22:31:48 +02:00
/*
* We check the catalog name and then ignore it.
*/
2003-06-27 16:45:32 +02:00
if (strcmp(catalogname, get_database_name(MyDatabaseId)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2004-08-29 07:07:03 +02:00
errmsg("cross-database references are not implemented: %s",
NameListToString(names))));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
2003-08-04 02:43:34 +02:00
errmsg("improper qualified name (too many dotted names): %s",
NameListToString(names))));
break;
}
*nspname_p = schemaname;
*objname_p = objname;
}
/*
* LookupExplicitNamespace
* 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.
*/
Oid
LookupExplicitNamespace(const char *nspname)
{
Oid namespaceId;
AclResult aclresult;
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(nspname),
0, 0, 0);
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema \"%s\" does not exist", nspname)));
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
nspname);
return namespaceId;
}
/*
* LookupCreationNamespace
* Look up the schema and verify we have CREATE rights on it.
*
* This is just like LookupExplicitNamespace except for the permission check.
*/
Oid
LookupCreationNamespace(const char *nspname)
{
Oid namespaceId;
AclResult aclresult;
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(nspname),
0, 0, 0);
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema \"%s\" does not exist", nspname)));
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
nspname);
return namespaceId;
}
/*
* QualifiedNameGetCreationNamespace
* Given a possibly-qualified name for an object (in List-of-Values
* format), determine what namespace the object should be created in.
* Also extract and return the object name (last component of list).
*
* Note: this does not apply any permissions check. Callers must check
* for CREATE rights on the selected namespace when appropriate.
*
* This is *not* used for tables. Hence, the TEMP table namespace is
* never selected as the creation target.
*/
Oid
QualifiedNameGetCreationNamespace(List *names, char **objname_p)
{
char *schemaname;
char *objname;
Oid namespaceId;
/* deconstruct the name list */
DeconstructQualifiedName(names, &schemaname, &objname);
if (schemaname)
{
/* use exact schema given */
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(schemaname),
0, 0, 0);
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema \"%s\" does not exist", schemaname)));
/* we do not check for USAGE rights here! */
}
else
{
/* use the default creation namespace */
recomputeNamespacePath();
namespaceId = defaultCreationNamespace;
if (!OidIsValid(namespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("no schema has been selected to create in")));
}
*objname_p = objname;
return namespaceId;
}
/*
* makeRangeVarFromNameList
* Utility routine to convert a qualified-name list into RangeVar form.
*/
RangeVar *
makeRangeVarFromNameList(List *names)
{
RangeVar *rel = makeRangeVar(NULL, NULL);
switch (list_length(names))
{
case 1:
rel->relname = strVal(linitial(names));
break;
case 2:
rel->schemaname = strVal(linitial(names));
rel->relname = strVal(lsecond(names));
break;
case 3:
rel->catalogname = strVal(linitial(names));
rel->schemaname = strVal(lsecond(names));
rel->relname = strVal(lthird(names));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
2003-08-04 02:43:34 +02:00
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
break;
}
return rel;
}
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
*
* This is used primarily to form error messages, and so we do not quote
* the list elements, for the sake of legibility.
*/
char *
NameListToString(List *names)
{
StringInfoData string;
ListCell *l;
initStringInfo(&string);
foreach(l, names)
{
if (l != list_head(names))
appendStringInfoChar(&string, '.');
appendStringInfoString(&string, strVal(lfirst(l)));
}
return string.data;
}
/*
* NameListToQuotedString
* Utility routine to convert a qualified-name list into a string.
*
* Same as above except that names will be double-quoted where necessary,
* so the string could be re-parsed (eg, by textToQualifiedNameList).
*/
char *
NameListToQuotedString(List *names)
{
StringInfoData string;
ListCell *l;
initStringInfo(&string);
foreach(l, names)
{
if (l != list_head(names))
appendStringInfoChar(&string, '.');
appendStringInfoString(&string, quote_identifier(strVal(lfirst(l))));
}
return string.data;
}
/*
* isTempNamespace - is the given namespace my temporary-table namespace?
*/
bool
isTempNamespace(Oid namespaceId)
{
if (OidIsValid(myTempNamespace) && myTempNamespace == namespaceId)
return true;
return false;
}
/*
* isAnyTempNamespace - is the given namespace a temporary-table namespace
* (either my own, or another backend's)?
*/
bool
isAnyTempNamespace(Oid namespaceId)
{
bool result;
char *nspname;
/* If the namespace name starts with "pg_temp_", say "true" */
nspname = get_namespace_name(namespaceId);
if (!nspname)
return false; /* no such namespace? */
result = (strncmp(nspname, "pg_temp_", 8) == 0);
pfree(nspname);
return result;
}
/*
* isOtherTempNamespace - is the given namespace some other backend's
* temporary-table namespace?
*/
bool
isOtherTempNamespace(Oid namespaceId)
{
/* If it's my own temp namespace, say "false" */
if (isTempNamespace(namespaceId))
return false;
/* Else, if the namespace name starts with "pg_temp_", say "true" */
return isAnyTempNamespace(namespaceId);
}
/*
* PushSpecialNamespace - push a "special" namespace onto the front of the
* search path.
*
* This is a slightly messy hack intended only for support of CREATE SCHEMA.
* Although the API is defined to allow a stack of pushed namespaces, we
* presently only support one at a time.
*
* The pushed namespace will be removed from the search path at end of
* transaction, whether commit or abort.
*/
void
PushSpecialNamespace(Oid namespaceId)
{
Assert(!OidIsValid(mySpecialNamespace));
mySpecialNamespace = namespaceId;
namespaceSearchPathValid = false;
}
/*
* PopSpecialNamespace - remove previously pushed special namespace.
*/
void
PopSpecialNamespace(Oid namespaceId)
{
Assert(mySpecialNamespace == namespaceId);
mySpecialNamespace = InvalidOid;
namespaceSearchPathValid = false;
}
2002-08-06 07:40:47 +02:00
/*
* FindConversionByName - find a conversion by possibly qualified name
*/
2002-09-04 22:31:48 +02:00
Oid
FindConversionByName(List *name)
2002-08-06 07:40:47 +02:00
{
char *schemaname;
2002-09-04 22:31:48 +02:00
char *conversion_name;
Oid namespaceId;
Oid conoid;
ListCell *l;
2002-08-06 07:40:47 +02:00
/* deconstruct the name list */
DeconstructQualifiedName(name, &schemaname, &conversion_name);
2002-08-06 07:40:47 +02:00
if (schemaname)
2002-08-06 07:40:47 +02:00
{
/* use exact schema given */
namespaceId = LookupExplicitNamespace(schemaname);
2002-08-06 07:40:47 +02:00
return FindConversion(conversion_name, namespaceId);
}
else
2002-08-06 07:40:47 +02:00
{
/* search for it in search path */
recomputeNamespacePath();
2002-08-06 07:40:47 +02:00
foreach(l, namespaceSearchPath)
{
namespaceId = lfirst_oid(l);
conoid = FindConversion(conversion_name, namespaceId);
if (OidIsValid(conoid))
return conoid;
}
2002-08-06 07:40:47 +02:00
}
/* Not found in path */
return InvalidOid;
}
2002-07-16 08:58:14 +02:00
/*
* FindDefaultConversionProc - find default encoding conversion proc
2002-07-16 08:58:14 +02:00
*/
2002-09-04 22:31:48 +02:00
Oid
FindDefaultConversionProc(int4 for_encoding, int4 to_encoding)
2002-07-16 08:58:14 +02:00
{
Oid proc;
ListCell *l;
2002-07-16 08:58:14 +02:00
recomputeNamespacePath();
foreach(l, namespaceSearchPath)
2002-07-16 08:58:14 +02:00
{
Oid namespaceId = lfirst_oid(l);
2002-07-16 08:58:14 +02:00
proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding);
if (OidIsValid(proc))
return proc;
}
/* Not found in path */
return InvalidOid;
}
/*
* recomputeNamespacePath - recompute path derived variables if needed.
*/
static void
recomputeNamespacePath(void)
{
Oid roleid = GetUserId();
char *rawname;
List *namelist;
List *oidlist;
List *newpath;
ListCell *l;
Oid firstNS;
MemoryContext oldcxt;
/*
* Do nothing if path is already valid.
*/
if (namespaceSearchPathValid && namespaceUser == roleid)
return;
/* Need a modifiable copy of namespace_search_path string */
rawname = pstrdup(namespace_search_path);
/* Parse string into list of identifiers */
if (!SplitIdentifierString(rawname, ',', &namelist))
{
/* syntax error in name list */
/* this should not happen if GUC checked check_search_path */
elog(ERROR, "invalid list syntax");
}
/*
* Convert the list of names to a list of OIDs. If any names are not
* recognizable or we don't have read access, just leave them out of
* the list. (We can't raise an error, since the search_path setting
2002-09-04 22:31:48 +02:00
* has already been accepted.) Don't make duplicate entries, either.
*/
oidlist = NIL;
foreach(l, namelist)
{
2002-09-04 22:31:48 +02:00
char *curname = (char *) lfirst(l);
Oid namespaceId;
if (strcmp(curname, "$user") == 0)
{
/* $user --- substitute namespace matching user name, if any */
HeapTuple tuple;
tuple = SearchSysCache(AUTHOID,
ObjectIdGetDatum(roleid),
0, 0, 0);
if (HeapTupleIsValid(tuple))
{
char *rname;
rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname);
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(rname),
0, 0, 0);
ReleaseSysCache(tuple);
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
pg_namespace_aclcheck(namespaceId, roleid,
ACL_USAGE) == ACLCHECK_OK)
oidlist = lappend_oid(oidlist, namespaceId);
}
}
else
{
/* normal namespace reference */
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(curname),
0, 0, 0);
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
pg_namespace_aclcheck(namespaceId, roleid,
ACL_USAGE) == ACLCHECK_OK)
oidlist = lappend_oid(oidlist, namespaceId);
}
}
/*
* Remember the first member of the explicit list.
*/
if (oidlist == NIL)
firstNS = InvalidOid;
else
firstNS = linitial_oid(oidlist);
/*
2002-09-04 22:31:48 +02:00
* 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 (!list_member_oid(oidlist, PG_CATALOG_NAMESPACE))
oidlist = lcons_oid(PG_CATALOG_NAMESPACE, oidlist);
if (OidIsValid(myTempNamespace) &&
!list_member_oid(oidlist, myTempNamespace))
oidlist = lcons_oid(myTempNamespace, oidlist);
if (OidIsValid(mySpecialNamespace) &&
!list_member_oid(oidlist, mySpecialNamespace))
oidlist = lcons_oid(mySpecialNamespace, oidlist);
/*
* Now that we've successfully built the new list of namespace OIDs,
* save it in permanent storage.
*/
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
newpath = list_copy(oidlist);
MemoryContextSwitchTo(oldcxt);
/* Now safe to assign to state variable. */
list_free(namespaceSearchPath);
namespaceSearchPath = newpath;
/*
* Update info derived from search path.
*/
firstExplicitNamespace = firstNS;
if (OidIsValid(mySpecialNamespace))
defaultCreationNamespace = mySpecialNamespace;
else
defaultCreationNamespace = firstNS;
/* Mark the path valid. */
namespaceSearchPathValid = true;
namespaceUser = roleid;
/* Clean up. */
pfree(rawname);
list_free(namelist);
list_free(oidlist);
}
/*
* InitTempTableNamespace
* Initialize temp table namespace on first use in a particular backend
*/
static void
InitTempTableNamespace(void)
{
char namespaceName[NAMEDATALEN];
Oid namespaceId;
/*
2002-09-04 22:31:48 +02:00
* First, do permission check to see if we are authorized to make temp
* tables. We use a nonstandard error message here since
* "databasename: permission denied" might be a tad cryptic.
*
2004-08-29 07:07:03 +02:00
* Note that ACL_CREATE_TEMP rights are rechecked in
* pg_namespace_aclmask; that's necessary since current user ID could
* change during the session. But there's no need to make the
* namespace in the first place until a temp table creation request is
* made by someone with appropriate rights.
*/
*) inet_(client|server)_(addr|port)() and necessary documentation for the four functions. > Also, please justify the temp-related changes. I was not aware that we > had any breakage there. patch-tmp-schema.txt contains the following bits: *) Changes pg_namespace_aclmask() so that the superuser is always able to create objects in the temp namespace. *) Changes pg_namespace_aclmask() so that if this is a temp namespace, objects are only allowed to be created in the temp namespace if the user has TEMP privs on the database. This encompasses all object creation, not just TEMP tables. *) InitTempTableNamespace() checks to see if the current user, not the session user, has access to create a temp namespace. The first two changes are necessary to support the third change. Now it's possible to revoke all temp table privs from non-super users and limiting all creation of temp tables/schemas via a function that's executed with elevated privs (security definer). Before this change, it was not possible to have a setuid function to create a temp table/schema if the session user had no TEMP privs. patch-area-path.txt contains: *) Can now determine the area of a closed path. patch-dfmgr.txt contains: *) Small tweak to add the library path that's being expanded. I was using $lib/foo.so and couldn't easily figure out what the error message, "invalid macro name in dynamic library path" meant without looking through the source code. With the path in there, at least I know where to start looking in my config file. Sean Chittenden
2004-05-26 20:35:51 +02:00
if (pg_database_aclcheck(MyDatabaseId, GetUserId(),
ACL_CREATE_TEMP) != ACLCHECK_OK)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create temporary tables in database \"%s\"",
get_database_name(MyDatabaseId))));
snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
namespaceId = GetSysCacheOid(NAMESPACENAME,
CStringGetDatum(namespaceName),
0, 0, 0);
if (!OidIsValid(namespaceId))
{
/*
* First use of this temp namespace in this database; create it.
* The temp namespaces are always owned by the superuser. We
2002-09-04 22:31:48 +02:00
* leave their permissions at default --- i.e., no access except
* to superuser --- to ensure that unprivileged users can't peek
* at other backends' temp tables. This works because the places
2002-09-04 22:31:48 +02:00
* that access the temp namespace for my own backend skip
* permissions checks on it.
*/
namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID);
/* Advance command counter to make namespace visible */
CommandCounterIncrement();
}
else
{
/*
* If the namespace already exists, clean it out (in case the
* former owner crashed without doing so).
*/
RemoveTempRelations(namespaceId);
}
/*
* Okay, we've prepared the temp namespace ... but it's not committed
* yet, so all our work could be undone by transaction rollback. Set
* flag for AtEOXact_Namespace to know what to do.
*/
myTempNamespace = namespaceId;
/* It should not be done already. */
AssertState(myTempNamespaceSubID == InvalidSubTransactionId);
myTempNamespaceSubID = GetCurrentSubTransactionId();
2002-09-04 22:31:48 +02:00
namespaceSearchPathValid = false; /* need to rebuild list */
}
/*
* End-of-transaction cleanup for namespaces.
*/
void
AtEOXact_Namespace(bool isCommit)
{
/*
* If we abort the transaction in which a temp namespace was selected,
* we'll have to do any creation or cleanout work over again. So,
2002-09-04 22:31:48 +02:00
* just forget the namespace entirely until next time. On the other
* hand, if we commit then register an exit callback to clean out the
* temp tables 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 (isCommit)
on_shmem_exit(RemoveTempRelationsCallback, 0);
else
{
myTempNamespace = InvalidOid;
2002-09-04 22:31:48 +02:00
namespaceSearchPathValid = false; /* need to rebuild list */
}
myTempNamespaceSubID = InvalidSubTransactionId;
}
2002-09-04 22:31:48 +02:00
/*
* Clean up if someone failed to do PopSpecialNamespace
*/
if (OidIsValid(mySpecialNamespace))
{
mySpecialNamespace = InvalidOid;
2002-09-04 22:31:48 +02:00
namespaceSearchPathValid = false; /* need to rebuild list */
}
}
/*
* AtEOSubXact_Namespace
*
* At subtransaction commit, propagate the temp-namespace-creation
* flag to the parent subtransaction.
*
* At subtransaction abort, forget the flag if we set it up.
*/
void
AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid)
{
if (myTempNamespaceSubID == mySubid)
{
if (isCommit)
myTempNamespaceSubID = parentSubid;
else
{
myTempNamespaceSubID = InvalidSubTransactionId;
/* TEMP namespace creation failed, so reset state */
myTempNamespace = InvalidOid;
namespaceSearchPathValid = false; /* need to rebuild list */
}
}
}
/*
* Remove all relations in the specified temp namespace.
*
* This is called at backend shutdown (if we made any temp relations).
* It is also called when we begin using a pre-existing temp namespace,
* in order to clean out any relations that might have been created by
* a crashed backend.
*/
static void
RemoveTempRelations(Oid tempNamespaceId)
{
ObjectAddress object;
/*
* We want to get rid of everything in the target namespace, but not
2003-08-04 02:43:34 +02:00
* the namespace itself (deleting it only to recreate it later would
* be a waste of cycles). We do this by finding everything that has a
* dependency on the namespace.
*/
object.classId = NamespaceRelationId;
object.objectId = tempNamespaceId;
object.objectSubId = 0;
deleteWhatDependsOn(&object, false);
}
/*
* Callback to remove temp relations at backend exit.
*/
static void
RemoveTempRelationsCallback(int code, Datum arg)
{
2002-09-04 22:31:48 +02:00
if (OidIsValid(myTempNamespace)) /* should always be true */
{
/* Need to ensure we have a usable transaction. */
AbortOutOfAnyTransaction();
StartTransactionCommand();
RemoveTempRelations(myTempNamespace);
CommitTransactionCommand();
}
}
/*
* Routines for handling the GUC variable 'search_path'.
*/
/* assign_hook: validate new search_path, do extra actions as needed */
const char *
assign_search_path(const char *newval, bool doit, GucSource source)
{
char *rawname;
List *namelist;
ListCell *l;
/* Need a modifiable copy of string */
rawname = pstrdup(newval);
/* Parse string into list of identifiers */
if (!SplitIdentifierString(rawname, ',', &namelist))
{
/* syntax error in name list */
pfree(rawname);
list_free(namelist);
return NULL;
}
/*
* If we aren't inside a transaction, we cannot do database access so
2002-09-04 22:31:48 +02:00
* cannot verify the individual names. Must accept the list on faith.
*/
if (source >= PGC_S_INTERACTIVE && IsTransactionState())
{
/*
* Verify that all the names are either valid namespace names or
* "$user". We do not require $user to correspond to a valid
2002-09-04 22:31:48 +02:00
* namespace. 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
2004-08-29 07:07:03 +02:00
* in the current database. We reduce the message to NOTICE
* instead.
*/
foreach(l, namelist)
{
2002-09-04 22:31:48 +02:00
char *curname = (char *) lfirst(l);
if (strcmp(curname, "$user") == 0)
continue;
if (!SearchSysCacheExists(NAMESPACENAME,
CStringGetDatum(curname),
0, 0, 0))
ereport((source == PGC_S_TEST) ? NOTICE : ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
2003-08-04 02:43:34 +02:00
errmsg("schema \"%s\" does not exist", curname)));
}
}
pfree(rawname);
list_free(namelist);
/*
2002-09-04 22:31:48 +02:00
* We mark the path as needing recomputation, but don't do anything
* until it's needed. This avoids trying to do database access during
* GUC initialization.
*/
if (doit)
namespaceSearchPathValid = false;
return newval;
}
/*
* InitializeSearchPath: initialize module during InitPostgres.
*
* This is called after we are up enough to be able to do catalog lookups.
*/
void
InitializeSearchPath(void)
{
if (IsBootstrapProcessingMode())
{
/*
* In bootstrap mode, the search path must be 'pg_catalog' so that
2002-09-04 22:31:48 +02:00
* tables are created in the proper namespace; ignore the GUC
* setting.
*/
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
namespaceSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE);
MemoryContextSwitchTo(oldcxt);
defaultCreationNamespace = PG_CATALOG_NAMESPACE;
firstExplicitNamespace = PG_CATALOG_NAMESPACE;
namespaceSearchPathValid = true;
namespaceUser = GetUserId();
}
else
{
/*
2002-09-04 22:31:48 +02:00
* In normal mode, arrange for a callback on any syscache
* invalidation of pg_namespace rows.
*/
CacheRegisterSyscacheCallback(NAMESPACEOID,
NamespaceCallback,
(Datum) 0);
/* Force search path to be recomputed on next use */
namespaceSearchPathValid = false;
}
}
/*
* NamespaceCallback
* Syscache inval callback function
*/
static void
NamespaceCallback(Datum arg, Oid relid)
{
/* Force search path to be recomputed on next use */
namespaceSearchPathValid = false;
}
/*
* Fetch the active search path. The return value is a palloc'ed list
* of OIDs; the caller is responsible for freeing this storage as
* appropriate.
*
* The returned list includes the implicitly-prepended namespaces only if
* includeImplicit is true.
*/
List *
fetch_search_path(bool includeImplicit)
{
List *result;
recomputeNamespacePath();
result = list_copy(namespaceSearchPath);
if (!includeImplicit)
{
while (result && linitial_oid(result) != firstExplicitNamespace)
result = list_delete_first(result);
}
return result;
}
/*
* Export the FooIsVisible functions as SQL-callable functions.
*/
Datum
pg_table_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(RelationIsVisible(oid));
}
Datum
pg_type_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(TypeIsVisible(oid));
}
Datum
pg_function_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(FunctionIsVisible(oid));
}
Datum
pg_operator_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(OperatorIsVisible(oid));
}
Datum
pg_opclass_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(OpclassIsVisible(oid));
}
Datum
pg_conversion_is_visible(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
PG_RETURN_BOOL(ConversionIsVisible(oid));
}