diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/catalog/aclchk.c | 4 | ||||
-rw-r--r-- | src/backend/catalog/namespace.c | 206 | ||||
-rw-r--r-- | src/test/regress/expected/temp.out | 58 | ||||
-rw-r--r-- | src/test/regress/sql/temp.sql | 33 |
4 files changed, 272 insertions, 29 deletions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index b837ba4e91c..292a737099e 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.138 2007/03/26 16:58:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.139 2007/04/20 02:37:37 tgl Exp $ * * NOTES * See acl.h. @@ -1833,7 +1833,7 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid, */ if (isTempNamespace(nsp_oid)) { - if (pg_database_aclcheck(MyDatabaseId, GetUserId(), + if (pg_database_aclcheck(MyDatabaseId, roleid, ACL_CREATE_TEMP) == ACLCHECK_OK) return mask & ACL_ALL_RIGHTS_NAMESPACE; else diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index ca51b997c0b..55379b66f65 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.95 2007/04/12 22:34:45 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.96 2007/04/20 02:37:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -62,12 +62,30 @@ * SQL99. Also, this provides a way to search the system namespace first * without thereby making it the default creation target namespace.) * + * For security reasons, searches using the search path will ignore the temp + * namespace when searching for any object type other than relations and + * types. (We must allow types since temp tables have rowtypes.) + * * The default creation target namespace is always the first element of the * explicit list. If the explicit list is empty, there is no default target. * - * In bootstrap mode, the search path is set equal to 'pg_catalog', so that + * The textual specification of search_path can include "$user" to refer to + * the namespace named the same as the current user, if any. (This is just + * ignored if there is no such namespace.) Also, it can include "pg_temp" + * 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 + * 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.) + * activeTempCreationPending is TRUE if "pg_temp" appears first in the string + * but is not reflected in activeCreationNamespace because the namespace isn't + * set up yet. + * + * 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 + * initdb is also careful to set search_path to "pg_catalog" for 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 @@ -102,15 +120,20 @@ static List *activeSearchPath = NIL; /* default place to create stuff; if InvalidOid, no default */ static Oid activeCreationNamespace = InvalidOid; +/* if TRUE, activeCreationNamespace is wrong, it should be temp namespace */ +static bool activeTempCreationPending = false; + /* These variables are the values last derived from namespace_search_path: */ static List *baseSearchPath = NIL; static Oid baseCreationNamespace = InvalidOid; +static bool baseTempCreationPending = false; + static Oid namespaceUser = InvalidOid; -/* The above three values are valid only if baseSearchPathValid */ +/* The above four values are valid only if baseSearchPathValid */ static bool baseSearchPathValid = true; /* Override requests are remembered in a stack of OverrideStackEntry structs */ @@ -262,6 +285,14 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation) if (newRelation->schemaname) { + /* check for pg_temp alias */ + if (strcmp(newRelation->schemaname, "pg_temp") == 0) + { + /* Initialize temp namespace if first time through */ + if (!OidIsValid(myTempNamespace)) + InitTempTableNamespace(); + return myTempNamespace; + } /* use exact schema given */ namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(newRelation->schemaname), @@ -277,6 +308,12 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation) { /* use the default creation namespace */ recomputeNamespacePath(); + if (activeTempCreationPending) + { + /* Need to initialize temp namespace */ + InitTempTableNamespace(); + return myTempNamespace; + } namespaceId = activeCreationNamespace; if (!OidIsValid(namespaceId)) ereport(ERROR, @@ -549,12 +586,16 @@ FuncnameGetCandidates(List *names, int nargs) } else { - /* Consider only procs that are in the search path */ + /* + * Consider only procs that are in the search path and are not + * in the temp namespace. + */ ListCell *nsp; foreach(nsp, activeSearchPath) { - if (procform->pronamespace == lfirst_oid(nsp)) + if (procform->pronamespace == lfirst_oid(nsp) && + procform->pronamespace != myTempNamespace) break; pathpos++; } @@ -770,6 +811,9 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright) Oid namespaceId = lfirst_oid(l); int i; + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + for (i = 0; i < catlist->n_members; i++) { HeapTuple opertup = &catlist->members[i]->tuple; @@ -872,12 +916,16 @@ OpernameGetCandidates(List *names, char oprkind) } else { - /* Consider only opers that are in the search path */ + /* + * Consider only opers that are in the search path and are not + * in the temp namespace. + */ ListCell *nsp; foreach(nsp, activeSearchPath) { - if (operform->oprnamespace == lfirst_oid(nsp)) + if (operform->oprnamespace == lfirst_oid(nsp) && + operform->oprnamespace != myTempNamespace) break; pathpos++; } @@ -1025,6 +1073,9 @@ OpclassnameGetOpcid(Oid amid, const char *opcname) { Oid namespaceId = lfirst_oid(l); + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + opcid = GetSysCacheOid(CLAAMNAMENSP, ObjectIdGetDatum(amid), PointerGetDatum(opcname), @@ -1108,6 +1159,9 @@ OpfamilynameGetOpfid(Oid amid, const char *opfname) { Oid namespaceId = lfirst_oid(l); + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + opfid = GetSysCacheOid(OPFAMILYAMNAMENSP, ObjectIdGetDatum(amid), PointerGetDatum(opfname), @@ -1190,6 +1244,9 @@ ConversionGetConid(const char *conname) { Oid namespaceId = lfirst_oid(l); + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + conid = GetSysCacheOid(CONNAMENSP, PointerGetDatum(conname), ObjectIdGetDatum(namespaceId), @@ -1316,6 +1373,19 @@ LookupExplicitNamespace(const char *nspname) Oid namespaceId; AclResult aclresult; + /* check for pg_temp alias */ + if (strcmp(nspname, "pg_temp") == 0) + { + if (OidIsValid(myTempNamespace)) + return myTempNamespace; + /* + * 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. + */ + } + namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(nspname), 0, 0, 0); @@ -1336,7 +1406,11 @@ LookupExplicitNamespace(const char *nspname) * LookupCreationNamespace * Look up the schema and verify we have CREATE rights on it. * - * This is just like LookupExplicitNamespace except for the permission check. + * This is just like LookupExplicitNamespace except for the permission check, + * and that we are willing to create pg_temp if needed. + * + * Note: calling this may result in a CommandCounterIncrement operation, + * if we have to create or clean out the temp namespace. */ Oid LookupCreationNamespace(const char *nspname) @@ -1344,6 +1418,15 @@ LookupCreationNamespace(const char *nspname) Oid namespaceId; AclResult aclresult; + /* check for pg_temp alias */ + if (strcmp(nspname, "pg_temp") == 0) + { + /* Initialize temp namespace if first time through */ + if (!OidIsValid(myTempNamespace)) + InitTempTableNamespace(); + return myTempNamespace; + } + namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(nspname), 0, 0, 0); @@ -1369,21 +1452,28 @@ LookupCreationNamespace(const char *nspname) * 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. + * Note: calling this may result in a CommandCounterIncrement operation, + * if we have to create or clean out the temp namespace. */ Oid QualifiedNameGetCreationNamespace(List *names, char **objname_p) { char *schemaname; - char *objname; Oid namespaceId; /* deconstruct the name list */ - DeconstructQualifiedName(names, &schemaname, &objname); + DeconstructQualifiedName(names, &schemaname, objname_p); if (schemaname) { + /* check for pg_temp alias */ + if (strcmp(schemaname, "pg_temp") == 0) + { + /* Initialize temp namespace if first time through */ + if (!OidIsValid(myTempNamespace)) + InitTempTableNamespace(); + return myTempNamespace; + } /* use exact schema given */ namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(schemaname), @@ -1398,6 +1488,12 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) { /* use the default creation namespace */ recomputeNamespacePath(); + if (activeTempCreationPending) + { + /* Need to initialize temp namespace */ + InitTempTableNamespace(); + return myTempNamespace; + } namespaceId = activeCreationNamespace; if (!OidIsValid(namespaceId)) ereport(ERROR, @@ -1405,7 +1501,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) errmsg("no schema has been selected to create in"))); } - *objname_p = objname; return namespaceId; } @@ -1634,6 +1729,7 @@ PushOverrideSearchPath(OverrideSearchPath *newpath) /* And make it active. */ activeSearchPath = entry->searchPath; activeCreationNamespace = entry->creationNamespace; + activeTempCreationPending = false; /* XXX is this OK? */ MemoryContextSwitchTo(oldcxt); } @@ -1667,12 +1763,14 @@ PopOverrideSearchPath(void) entry = (OverrideStackEntry *) linitial(overrideStack); activeSearchPath = entry->searchPath; activeCreationNamespace = entry->creationNamespace; + activeTempCreationPending = false; /* XXX is this OK? */ } else { /* If not baseSearchPathValid, this is useless but harmless */ activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; + activeTempCreationPending = baseTempCreationPending; } } @@ -1706,6 +1804,10 @@ FindConversionByName(List *name) foreach(l, activeSearchPath) { namespaceId = lfirst_oid(l); + + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + conoid = FindConversion(conversion_name, namespaceId); if (OidIsValid(conoid)) return conoid; @@ -1731,6 +1833,9 @@ FindDefaultConversionProc(int4 for_encoding, int4 to_encoding) { Oid namespaceId = lfirst_oid(l); + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding); if (OidIsValid(proc)) return proc; @@ -1752,6 +1857,7 @@ recomputeNamespacePath(void) List *oidlist; List *newpath; ListCell *l; + bool temp_missing; Oid firstNS; MemoryContext oldcxt; @@ -1781,6 +1887,7 @@ recomputeNamespacePath(void) * already been accepted.) Don't make duplicate entries, either. */ oidlist = NIL; + temp_missing = false; foreach(l, namelist) { char *curname = (char *) lfirst(l); @@ -1810,6 +1917,21 @@ recomputeNamespacePath(void) oidlist = lappend_oid(oidlist, namespaceId); } } + else if (strcmp(curname, "pg_temp") == 0) + { + /* pg_temp --- substitute temp namespace, if any */ + if (OidIsValid(myTempNamespace)) + { + if (!list_member_oid(oidlist, myTempNamespace)) + oidlist = lappend_oid(oidlist, myTempNamespace); + } + else + { + /* If it ought to be the creation namespace, set flag */ + if (oidlist == NIL) + temp_missing = true; + } + } else { /* normal namespace reference */ @@ -1825,7 +1947,9 @@ recomputeNamespacePath(void) } /* - * Remember the first member of the explicit list. + * 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.) */ if (oidlist == NIL) firstNS = InvalidOid; @@ -1856,6 +1980,7 @@ recomputeNamespacePath(void) list_free(baseSearchPath); baseSearchPath = newpath; baseCreationNamespace = firstNS; + baseTempCreationPending = temp_missing; /* Mark the path valid. */ baseSearchPathValid = true; @@ -1864,6 +1989,7 @@ recomputeNamespacePath(void) /* And make it active. */ activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; + activeTempCreationPending = baseTempCreationPending; /* Clean up. */ pfree(rawname); @@ -1881,6 +2007,8 @@ InitTempTableNamespace(void) char namespaceName[NAMEDATALEN]; Oid namespaceId; + Assert(!OidIsValid(myTempNamespace)); + /* * First, do permission check to see if we are authorized to make temp * tables. We use a nonstandard error message here since "databasename: @@ -1941,16 +2069,6 @@ InitTempTableNamespace(void) } /* - * Remove all temp tables from the temporary namespace. - */ -void -ResetTempTableNamespace(void) -{ - if (OidIsValid(myTempNamespace)) - RemoveTempRelations(myTempNamespace); -} - -/* * End-of-transaction cleanup for namespaces. */ void @@ -1995,6 +2113,7 @@ AtEOXact_Namespace(bool isCommit) /* If not baseSearchPathValid, this is useless but harmless */ activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; + activeTempCreationPending = baseTempCreationPending; } } @@ -2046,12 +2165,14 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, entry = (OverrideStackEntry *) linitial(overrideStack); activeSearchPath = entry->searchPath; activeCreationNamespace = entry->creationNamespace; + activeTempCreationPending = false; /* XXX is this OK? */ } else { /* If not baseSearchPathValid, this is useless but harmless */ activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; + activeTempCreationPending = baseTempCreationPending; } } @@ -2099,6 +2220,16 @@ RemoveTempRelationsCallback(int code, Datum arg) } } +/* + * Remove all temp tables from the temporary namespace. + */ +void +ResetTempTableNamespace(void) +{ + if (OidIsValid(myTempNamespace)) + RemoveTempRelations(myTempNamespace); +} + /* * Routines for handling the GUC variable 'search_path'. @@ -2132,8 +2263,9 @@ assign_search_path(const char *newval, bool doit, GucSource source) { /* * Verify that all the names are either valid namespace names or - * "$user". We do not require $user to correspond to a valid - * namespace. We do not check for USAGE rights, either; should we? + * "$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 @@ -2147,6 +2279,8 @@ assign_search_path(const char *newval, bool doit, GucSource source) if (strcmp(curname, "$user") == 0) continue; + if (strcmp(curname, "pg_temp") == 0) + continue; if (!SearchSysCacheExists(NAMESPACENAME, CStringGetDatum(curname), 0, 0, 0)) @@ -2190,10 +2324,12 @@ InitializeSearchPath(void) baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE); MemoryContextSwitchTo(oldcxt); baseCreationNamespace = PG_CATALOG_NAMESPACE; + baseTempCreationPending = false; baseSearchPathValid = true; namespaceUser = GetUserId(); activeSearchPath = baseSearchPath; activeCreationNamespace = baseCreationNamespace; + activeTempCreationPending = baseTempCreationPending; } else { @@ -2227,6 +2363,9 @@ NamespaceCallback(Datum arg, Oid relid) * * The returned list includes the implicitly-prepended namespaces only if * includeImplicit is true. + * + * Note: calling this may result in a CommandCounterIncrement operation, + * if we have to create or clean out the temp namespace. */ List * fetch_search_path(bool includeImplicit) @@ -2235,6 +2374,19 @@ fetch_search_path(bool includeImplicit) recomputeNamespacePath(); + /* + * 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 + * current_schema() is supposedly a stable function without side-effects, + * but the alternatives seem worse. + */ + if (activeTempCreationPending) + { + InitTempTableNamespace(); + recomputeNamespacePath(); + } + result = list_copy(activeSearchPath); if (!includeImplicit) { diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index 3ba19b55a81..335a48c7272 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -137,3 +137,61 @@ CREATE TEMP TABLE temptest4(col int REFERENCES temptest3); COMMIT; ERROR: unsupported ON COMMIT and foreign key combination DETAIL: Table "temptest4" references "temptest3", but they do not have the same ON COMMIT setting. +-- Test manipulation of temp schema's placement in search path +create table public.whereami (f1 text); +insert into public.whereami values ('public'); +create temp table whereami (f1 text); +insert into whereami values ('temp'); +create function public.whoami() returns text + as $$select 'public'::text$$ language sql; +create function pg_temp.whoami() returns text + as $$select 'temp'::text$$ language sql; +-- default should have pg_temp implicitly first, but only for tables +select * from whereami; + f1 +------ + temp +(1 row) + +select whoami(); + whoami +-------- + public +(1 row) + +-- can list temp first explicitly, but it still doesn't affect functions +set search_path = pg_temp, public; +select * from whereami; + f1 +------ + temp +(1 row) + +select whoami(); + whoami +-------- + public +(1 row) + +-- or put it last for security +set search_path = public, pg_temp; +select * from whereami; + f1 +-------- + public +(1 row) + +select whoami(); + whoami +-------- + public +(1 row) + +-- you can invoke a temp function explicitly, though +select pg_temp.whoami(); + whoami +-------- + temp +(1 row) + +drop table public.whereami; diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql index 6a4b8561449..82d7834bd14 100644 --- a/src/test/regress/sql/temp.sql +++ b/src/test/regress/sql/temp.sql @@ -118,3 +118,36 @@ BEGIN; CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS; CREATE TEMP TABLE temptest4(col int REFERENCES temptest3); COMMIT; + +-- Test manipulation of temp schema's placement in search path + +create table public.whereami (f1 text); +insert into public.whereami values ('public'); + +create temp table whereami (f1 text); +insert into whereami values ('temp'); + +create function public.whoami() returns text + as $$select 'public'::text$$ language sql; + +create function pg_temp.whoami() returns text + as $$select 'temp'::text$$ language sql; + +-- default should have pg_temp implicitly first, but only for tables +select * from whereami; +select whoami(); + +-- can list temp first explicitly, but it still doesn't affect functions +set search_path = pg_temp, public; +select * from whereami; +select whoami(); + +-- or put it last for security +set search_path = public, pg_temp; +select * from whereami; +select whoami(); + +-- you can invoke a temp function explicitly, though +select pg_temp.whoami(); + +drop table public.whereami; |