mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-23 19:39:53 +08:00
Support explicit placement of the temporary-table schema within search_path.
This is needed to allow a security-definer function to set a truly secure value of search_path. Without it, a malicious user can use temporary objects to execute code with the privileges of the security-definer function. Even pushing the temp schema to the back of the search path is not quite good enough, because a function or operator at the back of the path might still capture control from one nearer the front due to having a more exact datatype match. Hence, disable searching the temp schema altogether for functions and operators. Security: CVE-2007-2138
This commit is contained in:
parent
2a1b76f046
commit
fc52d13ca6
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.36.2.7 2006/11/04 18:20:40 tgl Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.36.2.8 2007/04/20 02:38:04 tgl Exp $
|
||||||
-->
|
-->
|
||||||
<chapter Id="runtime-config">
|
<chapter Id="runtime-config">
|
||||||
<title>Server Configuration</title>
|
<title>Server Configuration</title>
|
||||||
@ -3103,9 +3103,17 @@ SELECT * FROM parent WHERE key = 2400;
|
|||||||
mentioned in the path then it will be searched in the specified
|
mentioned in the path then it will be searched in the specified
|
||||||
order. If <literal>pg_catalog</> is not in the path then it will
|
order. If <literal>pg_catalog</> is not in the path then it will
|
||||||
be searched <emphasis>before</> searching any of the path items.
|
be searched <emphasis>before</> searching any of the path items.
|
||||||
It should also be noted that the temporary-table schema,
|
</para>
|
||||||
<literal>pg_temp_<replaceable>nnn</></>, is implicitly searched before any of
|
|
||||||
these.
|
<para>
|
||||||
|
Likewise, the current session's temporary-table schema,
|
||||||
|
<literal>pg_temp_<replaceable>nnn</></>, is always searched if it
|
||||||
|
exists. It can be explicitly listed in the path by using the
|
||||||
|
alias <literal>pg_temp</>. If it is not listed in the path then
|
||||||
|
it is searched first (before even <literal>pg_catalog</>). However,
|
||||||
|
the temporary schema is only searched for relation (table, view,
|
||||||
|
sequence, etc) and data type names. It will never be searched for
|
||||||
|
function or operator names.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.67 2005/11/01 21:09:50 tgl Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.67.2.1 2007/04/20 02:38:04 tgl Exp $
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<refentry id="SQL-CREATEFUNCTION">
|
<refentry id="SQL-CREATEFUNCTION">
|
||||||
@ -473,6 +473,54 @@ SELECT * FROM dup(42);
|
|||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="sql-createfunction-security">
|
||||||
|
<title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Because a <literal>SECURITY DEFINER</literal> function is executed
|
||||||
|
with the privileges of the user that created it, care is needed to
|
||||||
|
ensure that the function cannot be misused. For security,
|
||||||
|
<xref linkend="guc-search-path"> should be set to exclude any schemas
|
||||||
|
writable by untrusted users. This prevents
|
||||||
|
malicious users from creating objects that mask objects used by the
|
||||||
|
function. Particularly important is in this regard is the
|
||||||
|
temporary-table schema, which is searched first by default, and
|
||||||
|
is normally writable by anyone. A secure arrangement can be had
|
||||||
|
by forcing the temporary schema to be searched last. To do this,
|
||||||
|
write <literal>pg_temp</> as the last entry in <varname>search_path</>.
|
||||||
|
This function illustrates safe usage:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION check_password(uname TEXT, pass TEXT)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
DECLARE passed BOOLEAN;
|
||||||
|
old_path TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Save old search_path; notice we must qualify current_setting
|
||||||
|
-- to ensure we invoke the right function
|
||||||
|
old_path := pg_catalog.current_setting('search_path');
|
||||||
|
|
||||||
|
-- Set a secure search_path: trusted schemas, then 'pg_temp'.
|
||||||
|
-- We set is_local = true so that the old value will be restored
|
||||||
|
-- in event of an error before we reach the function end.
|
||||||
|
PERFORM pg_catalog.set_config('search_path', 'admin, pg_temp', true);
|
||||||
|
|
||||||
|
-- Do whatever secure work we came for.
|
||||||
|
SELECT (pwd = $2) INTO passed
|
||||||
|
FROM pwds
|
||||||
|
WHERE username = $1;
|
||||||
|
|
||||||
|
-- Restore caller's search_path
|
||||||
|
PERFORM pg_catalog.set_config('search_path', old_path, true);
|
||||||
|
|
||||||
|
RETURN passed;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
|
||||||
<refsect1 id="sql-createfunction-compat">
|
<refsect1 id="sql-createfunction-compat">
|
||||||
<title>Compatibility</title>
|
<title>Compatibility</title>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.400.2.41 2007/04/19 13:02:30 momjian Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.400.2.42 2007/04/20 02:38:04 tgl Exp $ -->
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
Typical markup:
|
Typical markup:
|
||||||
@ -28,7 +28,8 @@ For new features, add links to the documentation sections.
|
|||||||
</note>
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
This release contains fixes from 8.1.8.
|
This release contains a variety of fixes from 8.1.8,
|
||||||
|
including a security fix.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -49,39 +50,57 @@ For new features, add links to the documentation sections.
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Fix <function>to_char()</> so it properly upper/lower cases localized day or month
|
Support explicit placement of the temporary-table schema within
|
||||||
names (Pavel Stehule)
|
<varname>search_path</>, and disable searching it for functions
|
||||||
|
and operators (Tom)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This is needed to allow a security-definer function to set a
|
||||||
|
truly secure value of <varname>search_path</>. Without it,
|
||||||
|
an unprivileged SQL user can use temporary objects to execute code
|
||||||
|
with the privileges of the security-definer function (CVE-2007-2138).
|
||||||
|
See <xref linkend="sql-createfunction"
|
||||||
|
endterm="sql-createfunction-title"> for more information.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<filename>/contrib/tsearch2</> fixes (Teodor)
|
<filename>/contrib/tsearch2</> crash fixes (Teodor)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Require <command>COMMIT TRANSACTION</> to be executed in the same database as
|
Require <command>COMMIT PREPARED</> to be executed in the same
|
||||||
it was prepared (Heikki)
|
database as the transaction was prepared in (Heikki)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Improve detection of <acronym>POSIX</>-style time zone names (Tom)
|
Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
|
||||||
|
<command>UPDATE</> chains (Tom, Pavan Deolasee)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
|
Planner fixes, including improving outer join and bitmap scan
|
||||||
|
selection logic (Tom)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Improve outer join and bitmap join selection logic (Tom)
|
Fix PANIC during enlargement of a hash index (bug introduced in 8.1.6)
|
||||||
|
(Tom)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Fix POSIX-style timezone specs to follow new USA DST rules (Tom)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
@ -3047,7 +3066,8 @@ psql -t -f fixseq.sql db1 | psql -e db1
|
|||||||
</note>
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
This release contains fixes from 8.0.12.
|
This release contains a variety of fixes from 8.0.12,
|
||||||
|
including a security fix.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -3068,25 +3088,43 @@ psql -t -f fixseq.sql db1 | psql -e db1
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<filename>/contrib/tsearch2</> fixes (Teodor)
|
Support explicit placement of the temporary-table schema within
|
||||||
|
<varname>search_path</>, and disable searching it for functions
|
||||||
|
and operators (Tom)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This is needed to allow a security-definer function to set a
|
||||||
|
truly secure value of <varname>search_path</>. Without it,
|
||||||
|
an unprivileged SQL user can use temporary objects to execute code
|
||||||
|
with the privileges of the security-definer function (CVE-2007-2138).
|
||||||
|
See <xref linkend="sql-createfunction"
|
||||||
|
endterm="sql-createfunction-title"> for more information.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Improve detection of <acronym>POSIX</>-style time zone names (Tom)
|
<filename>/contrib/tsearch2</> crash fixes (Teodor)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
|
Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
|
||||||
|
<command>UPDATE</> chains (Tom, Pavan Deolasee)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<filename>/contrib/tsearch2</> fixes (Teodor)
|
Fix PANIC during enlargement of a hash index (bug introduced in 8.0.10)
|
||||||
|
(Tom)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Fix POSIX-style timezone specs to follow new USA DST rules (Tom)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
@ -6537,7 +6575,8 @@ typedefs (Michael)</para></listitem>
|
|||||||
</note>
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
This release contains a variety of fixes from 7.4.16.
|
This release contains fixes from 7.4.16,
|
||||||
|
including a security fix.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -6558,13 +6597,37 @@ typedefs (Michael)</para></listitem>
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<filename>/contrib/tsearch2</> fixes (Teodor)
|
Support explicit placement of the temporary-table schema within
|
||||||
|
<varname>search_path</>, and disable searching it for functions
|
||||||
|
and operators (Tom)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This is needed to allow a security-definer function to set a
|
||||||
|
truly secure value of <varname>search_path</>. Without it,
|
||||||
|
an unprivileged SQL user can use temporary objects to execute code
|
||||||
|
with the privileges of the security-definer function (CVE-2007-2138).
|
||||||
|
See <xref linkend="sql-createfunction"
|
||||||
|
endterm="sql-createfunction-title"> for more information.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
|
<filename>/contrib/tsearch2</> crash fixes (Teodor)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
|
||||||
|
<command>UPDATE</> chains (Tom, Pavan Deolasee)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Fix PANIC during enlargement of a hash index (bug introduced in 7.4.15)
|
||||||
|
(Tom)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
@ -9697,7 +9760,8 @@ DROP SCHEMA information_schema CASCADE;
|
|||||||
</note>
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
This release contains a variety of fixes from 7.3.18.
|
This release contains fixes from 7.3.18,
|
||||||
|
including a security fix.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -9718,7 +9782,24 @@ DROP SCHEMA information_schema CASCADE;
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
|
Support explicit placement of the temporary-table schema within
|
||||||
|
<varname>search_path</>, and disable searching it for functions
|
||||||
|
and operators (Tom)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This is needed to allow a security-definer function to set a
|
||||||
|
truly secure value of <varname>search_path</>. Without it,
|
||||||
|
an unprivileged SQL user can use temporary objects to execute code
|
||||||
|
with the privileges of the security-definer function (CVE-2007-2138).
|
||||||
|
See <xref linkend="sql-createfunction"
|
||||||
|
endterm="sql-createfunction-title"> for more information.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
|
||||||
|
<command>UPDATE</> chains (Tom, Pavan Deolasee)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.120.2.1 2005/11/22 18:23:06 momjian Exp $
|
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.120.2.2 2007/04/20 02:38:04 tgl Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* See acl.h.
|
* See acl.h.
|
||||||
@ -1746,7 +1746,7 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
|
|||||||
*/
|
*/
|
||||||
if (isTempNamespace(nsp_oid))
|
if (isTempNamespace(nsp_oid))
|
||||||
{
|
{
|
||||||
if (pg_database_aclcheck(MyDatabaseId, GetUserId(),
|
if (pg_database_aclcheck(MyDatabaseId, roleid,
|
||||||
ACL_CREATE_TEMP) == ACLCHECK_OK)
|
ACL_CREATE_TEMP) == ACLCHECK_OK)
|
||||||
return mask & ACL_ALL_RIGHTS_NAMESPACE;
|
return mask & ACL_ALL_RIGHTS_NAMESPACE;
|
||||||
else
|
else
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.79.2.2 2006/02/10 19:01:22 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.79.2.3 2007/04/20 02:38:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -64,14 +64,32 @@
|
|||||||
* SQL99. Also, this provides a way to search the system namespace first
|
* SQL99. Also, this provides a way to search the system namespace first
|
||||||
* without thereby making it the default creation target namespace.)
|
* 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 normally equal to the first
|
* The default creation target namespace is normally equal to the first
|
||||||
* element of the explicit list, but is the "special" namespace when one
|
* 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
|
* has been set. If the explicit list is empty and there is no special
|
||||||
* namespace, there is no default target.
|
* namespace, 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.)
|
||||||
|
* tempCreationPending is TRUE if "pg_temp" appears first in the string but
|
||||||
|
* is not reflected in defaultCreationNamespace 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.
|
* 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
|
* The initdb script is also careful to set search_path to "pg_catalog" for
|
||||||
* its post-bootstrap standalone backend runs. Otherwise the default search
|
* its post-bootstrap standalone backend runs. Otherwise the default search
|
||||||
* path is determined by GUC. The factory default path contains the PUBLIC
|
* path is determined by GUC. The factory default path contains the PUBLIC
|
||||||
* namespace (if it exists), preceded by the user's personal namespace
|
* namespace (if it exists), preceded by the user's personal namespace
|
||||||
@ -99,7 +117,10 @@ static Oid defaultCreationNamespace = InvalidOid;
|
|||||||
/* first explicit member of list; usually same as defaultCreationNamespace */
|
/* first explicit member of list; usually same as defaultCreationNamespace */
|
||||||
static Oid firstExplicitNamespace = InvalidOid;
|
static Oid firstExplicitNamespace = InvalidOid;
|
||||||
|
|
||||||
/* The above four values are valid only if namespaceSearchPathValid */
|
/* if TRUE, defaultCreationNamespace is wrong, it should be temp namespace */
|
||||||
|
static bool tempCreationPending = false;
|
||||||
|
|
||||||
|
/* The above five values are valid only if namespaceSearchPathValid */
|
||||||
static bool namespaceSearchPathValid = true;
|
static bool namespaceSearchPathValid = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -244,6 +265,14 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
|
|||||||
|
|
||||||
if (newRelation->schemaname)
|
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 */
|
/* use exact schema given */
|
||||||
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||||
CStringGetDatum(newRelation->schemaname),
|
CStringGetDatum(newRelation->schemaname),
|
||||||
@ -259,6 +288,12 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
|
|||||||
{
|
{
|
||||||
/* use the default creation namespace */
|
/* use the default creation namespace */
|
||||||
recomputeNamespacePath();
|
recomputeNamespacePath();
|
||||||
|
if (tempCreationPending)
|
||||||
|
{
|
||||||
|
/* Need to initialize temp namespace */
|
||||||
|
InitTempTableNamespace();
|
||||||
|
return myTempNamespace;
|
||||||
|
}
|
||||||
namespaceId = defaultCreationNamespace;
|
namespaceId = defaultCreationNamespace;
|
||||||
if (!OidIsValid(namespaceId))
|
if (!OidIsValid(namespaceId))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -531,12 +566,16 @@ FuncnameGetCandidates(List *names, int nargs)
|
|||||||
}
|
}
|
||||||
else
|
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;
|
ListCell *nsp;
|
||||||
|
|
||||||
foreach(nsp, namespaceSearchPath)
|
foreach(nsp, namespaceSearchPath)
|
||||||
{
|
{
|
||||||
if (procform->pronamespace == lfirst_oid(nsp))
|
if (procform->pronamespace == lfirst_oid(nsp) &&
|
||||||
|
procform->pronamespace != myTempNamespace)
|
||||||
break;
|
break;
|
||||||
pathpos++;
|
pathpos++;
|
||||||
}
|
}
|
||||||
@ -765,12 +804,16 @@ OpernameGetCandidates(List *names, char oprkind)
|
|||||||
}
|
}
|
||||||
else
|
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;
|
ListCell *nsp;
|
||||||
|
|
||||||
foreach(nsp, namespaceSearchPath)
|
foreach(nsp, namespaceSearchPath)
|
||||||
{
|
{
|
||||||
if (operform->oprnamespace == lfirst_oid(nsp))
|
if (operform->oprnamespace == lfirst_oid(nsp) &&
|
||||||
|
operform->oprnamespace != myTempNamespace)
|
||||||
break;
|
break;
|
||||||
pathpos++;
|
pathpos++;
|
||||||
}
|
}
|
||||||
@ -931,6 +974,9 @@ OpclassnameGetOpcid(Oid amid, const char *opcname)
|
|||||||
{
|
{
|
||||||
Oid namespaceId = lfirst_oid(l);
|
Oid namespaceId = lfirst_oid(l);
|
||||||
|
|
||||||
|
if (namespaceId == myTempNamespace)
|
||||||
|
continue; /* do not look in temp namespace */
|
||||||
|
|
||||||
opcid = GetSysCacheOid(CLAAMNAMENSP,
|
opcid = GetSysCacheOid(CLAAMNAMENSP,
|
||||||
ObjectIdGetDatum(amid),
|
ObjectIdGetDatum(amid),
|
||||||
PointerGetDatum(opcname),
|
PointerGetDatum(opcname),
|
||||||
@ -1013,6 +1059,9 @@ ConversionGetConid(const char *conname)
|
|||||||
{
|
{
|
||||||
Oid namespaceId = lfirst_oid(l);
|
Oid namespaceId = lfirst_oid(l);
|
||||||
|
|
||||||
|
if (namespaceId == myTempNamespace)
|
||||||
|
continue; /* do not look in temp namespace */
|
||||||
|
|
||||||
conid = GetSysCacheOid(CONNAMENSP,
|
conid = GetSysCacheOid(CONNAMENSP,
|
||||||
PointerGetDatum(conname),
|
PointerGetDatum(conname),
|
||||||
ObjectIdGetDatum(namespaceId),
|
ObjectIdGetDatum(namespaceId),
|
||||||
@ -1139,6 +1188,19 @@ LookupExplicitNamespace(const char *nspname)
|
|||||||
Oid namespaceId;
|
Oid namespaceId;
|
||||||
AclResult aclresult;
|
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,
|
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||||
CStringGetDatum(nspname),
|
CStringGetDatum(nspname),
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
@ -1159,7 +1221,11 @@ LookupExplicitNamespace(const char *nspname)
|
|||||||
* LookupCreationNamespace
|
* LookupCreationNamespace
|
||||||
* Look up the schema and verify we have CREATE rights on it.
|
* 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
|
Oid
|
||||||
LookupCreationNamespace(const char *nspname)
|
LookupCreationNamespace(const char *nspname)
|
||||||
@ -1167,6 +1233,15 @@ LookupCreationNamespace(const char *nspname)
|
|||||||
Oid namespaceId;
|
Oid namespaceId;
|
||||||
AclResult aclresult;
|
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,
|
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||||
CStringGetDatum(nspname),
|
CStringGetDatum(nspname),
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
@ -1192,21 +1267,28 @@ LookupCreationNamespace(const char *nspname)
|
|||||||
* Note: this does not apply any permissions check. Callers must check
|
* Note: this does not apply any permissions check. Callers must check
|
||||||
* for CREATE rights on the selected namespace when appropriate.
|
* for CREATE rights on the selected namespace when appropriate.
|
||||||
*
|
*
|
||||||
* This is *not* used for tables. Hence, the TEMP table namespace is
|
* Note: calling this may result in a CommandCounterIncrement operation,
|
||||||
* never selected as the creation target.
|
* if we have to create or clean out the temp namespace.
|
||||||
*/
|
*/
|
||||||
Oid
|
Oid
|
||||||
QualifiedNameGetCreationNamespace(List *names, char **objname_p)
|
QualifiedNameGetCreationNamespace(List *names, char **objname_p)
|
||||||
{
|
{
|
||||||
char *schemaname;
|
char *schemaname;
|
||||||
char *objname;
|
|
||||||
Oid namespaceId;
|
Oid namespaceId;
|
||||||
|
|
||||||
/* deconstruct the name list */
|
/* deconstruct the name list */
|
||||||
DeconstructQualifiedName(names, &schemaname, &objname);
|
DeconstructQualifiedName(names, &schemaname, objname_p);
|
||||||
|
|
||||||
if (schemaname)
|
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 */
|
/* use exact schema given */
|
||||||
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
namespaceId = GetSysCacheOid(NAMESPACENAME,
|
||||||
CStringGetDatum(schemaname),
|
CStringGetDatum(schemaname),
|
||||||
@ -1221,6 +1303,12 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
|
|||||||
{
|
{
|
||||||
/* use the default creation namespace */
|
/* use the default creation namespace */
|
||||||
recomputeNamespacePath();
|
recomputeNamespacePath();
|
||||||
|
if (tempCreationPending)
|
||||||
|
{
|
||||||
|
/* Need to initialize temp namespace */
|
||||||
|
InitTempTableNamespace();
|
||||||
|
return myTempNamespace;
|
||||||
|
}
|
||||||
namespaceId = defaultCreationNamespace;
|
namespaceId = defaultCreationNamespace;
|
||||||
if (!OidIsValid(namespaceId))
|
if (!OidIsValid(namespaceId))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1228,7 +1316,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
|
|||||||
errmsg("no schema has been selected to create in")));
|
errmsg("no schema has been selected to create in")));
|
||||||
}
|
}
|
||||||
|
|
||||||
*objname_p = objname;
|
|
||||||
return namespaceId;
|
return namespaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1419,6 +1506,10 @@ FindConversionByName(List *name)
|
|||||||
foreach(l, namespaceSearchPath)
|
foreach(l, namespaceSearchPath)
|
||||||
{
|
{
|
||||||
namespaceId = lfirst_oid(l);
|
namespaceId = lfirst_oid(l);
|
||||||
|
|
||||||
|
if (namespaceId == myTempNamespace)
|
||||||
|
continue; /* do not look in temp namespace */
|
||||||
|
|
||||||
conoid = FindConversion(conversion_name, namespaceId);
|
conoid = FindConversion(conversion_name, namespaceId);
|
||||||
if (OidIsValid(conoid))
|
if (OidIsValid(conoid))
|
||||||
return conoid;
|
return conoid;
|
||||||
@ -1444,6 +1535,9 @@ FindDefaultConversionProc(int4 for_encoding, int4 to_encoding)
|
|||||||
{
|
{
|
||||||
Oid namespaceId = lfirst_oid(l);
|
Oid namespaceId = lfirst_oid(l);
|
||||||
|
|
||||||
|
if (namespaceId == myTempNamespace)
|
||||||
|
continue; /* do not look in temp namespace */
|
||||||
|
|
||||||
proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding);
|
proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding);
|
||||||
if (OidIsValid(proc))
|
if (OidIsValid(proc))
|
||||||
return proc;
|
return proc;
|
||||||
@ -1465,6 +1559,7 @@ recomputeNamespacePath(void)
|
|||||||
List *oidlist;
|
List *oidlist;
|
||||||
List *newpath;
|
List *newpath;
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
|
bool temp_missing;
|
||||||
Oid firstNS;
|
Oid firstNS;
|
||||||
MemoryContext oldcxt;
|
MemoryContext oldcxt;
|
||||||
|
|
||||||
@ -1492,6 +1587,7 @@ recomputeNamespacePath(void)
|
|||||||
* already been accepted.) Don't make duplicate entries, either.
|
* already been accepted.) Don't make duplicate entries, either.
|
||||||
*/
|
*/
|
||||||
oidlist = NIL;
|
oidlist = NIL;
|
||||||
|
temp_missing = false;
|
||||||
foreach(l, namelist)
|
foreach(l, namelist)
|
||||||
{
|
{
|
||||||
char *curname = (char *) lfirst(l);
|
char *curname = (char *) lfirst(l);
|
||||||
@ -1521,6 +1617,21 @@ recomputeNamespacePath(void)
|
|||||||
oidlist = lappend_oid(oidlist, namespaceId);
|
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
|
else
|
||||||
{
|
{
|
||||||
/* normal namespace reference */
|
/* normal namespace reference */
|
||||||
@ -1536,7 +1647,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)
|
if (oidlist == NIL)
|
||||||
firstNS = InvalidOid;
|
firstNS = InvalidOid;
|
||||||
@ -1576,9 +1689,16 @@ recomputeNamespacePath(void)
|
|||||||
*/
|
*/
|
||||||
firstExplicitNamespace = firstNS;
|
firstExplicitNamespace = firstNS;
|
||||||
if (OidIsValid(mySpecialNamespace))
|
if (OidIsValid(mySpecialNamespace))
|
||||||
|
{
|
||||||
defaultCreationNamespace = mySpecialNamespace;
|
defaultCreationNamespace = mySpecialNamespace;
|
||||||
|
/* don't have to create temp in this state */
|
||||||
|
tempCreationPending = false;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
defaultCreationNamespace = firstNS;
|
defaultCreationNamespace = firstNS;
|
||||||
|
tempCreationPending = temp_missing;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark the path valid. */
|
/* Mark the path valid. */
|
||||||
namespaceSearchPathValid = true;
|
namespaceSearchPathValid = true;
|
||||||
@ -1600,6 +1720,8 @@ InitTempTableNamespace(void)
|
|||||||
char namespaceName[NAMEDATALEN];
|
char namespaceName[NAMEDATALEN];
|
||||||
Oid namespaceId;
|
Oid namespaceId;
|
||||||
|
|
||||||
|
Assert(!OidIsValid(myTempNamespace));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* First, do permission check to see if we are authorized to make temp
|
* 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:
|
||||||
@ -1798,8 +1920,9 @@ assign_search_path(const char *newval, bool doit, GucSource source)
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Verify that all the names are either valid namespace names or
|
* Verify that all the names are either valid namespace names or
|
||||||
* "$user". We do not require $user to correspond to a valid
|
* "$user" or "pg_temp". We do not require $user to correspond to a
|
||||||
* namespace. We do not check for USAGE rights, either; should we?
|
* 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
|
* 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
|
* DATABASE SET or ALTER USER SET command. It could be that the
|
||||||
@ -1813,6 +1936,8 @@ assign_search_path(const char *newval, bool doit, GucSource source)
|
|||||||
|
|
||||||
if (strcmp(curname, "$user") == 0)
|
if (strcmp(curname, "$user") == 0)
|
||||||
continue;
|
continue;
|
||||||
|
if (strcmp(curname, "pg_temp") == 0)
|
||||||
|
continue;
|
||||||
if (!SearchSysCacheExists(NAMESPACENAME,
|
if (!SearchSysCacheExists(NAMESPACENAME,
|
||||||
CStringGetDatum(curname),
|
CStringGetDatum(curname),
|
||||||
0, 0, 0))
|
0, 0, 0))
|
||||||
@ -1857,6 +1982,7 @@ InitializeSearchPath(void)
|
|||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
defaultCreationNamespace = PG_CATALOG_NAMESPACE;
|
defaultCreationNamespace = PG_CATALOG_NAMESPACE;
|
||||||
firstExplicitNamespace = PG_CATALOG_NAMESPACE;
|
firstExplicitNamespace = PG_CATALOG_NAMESPACE;
|
||||||
|
tempCreationPending = false;
|
||||||
namespaceSearchPathValid = true;
|
namespaceSearchPathValid = true;
|
||||||
namespaceUser = GetUserId();
|
namespaceUser = GetUserId();
|
||||||
}
|
}
|
||||||
@ -1892,6 +2018,9 @@ NamespaceCallback(Datum arg, Oid relid)
|
|||||||
*
|
*
|
||||||
* The returned list includes the implicitly-prepended namespaces only if
|
* The returned list includes the implicitly-prepended namespaces only if
|
||||||
* includeImplicit is true.
|
* includeImplicit is true.
|
||||||
|
*
|
||||||
|
* Note: calling this may result in a CommandCounterIncrement operation,
|
||||||
|
* if we have to create or clean out the temp namespace.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
fetch_search_path(bool includeImplicit)
|
fetch_search_path(bool includeImplicit)
|
||||||
@ -1900,6 +2029,19 @@ fetch_search_path(bool includeImplicit)
|
|||||||
|
|
||||||
recomputeNamespacePath();
|
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 (tempCreationPending)
|
||||||
|
{
|
||||||
|
InitTempTableNamespace();
|
||||||
|
recomputeNamespacePath();
|
||||||
|
}
|
||||||
|
|
||||||
result = list_copy(namespaceSearchPath);
|
result = list_copy(namespaceSearchPath);
|
||||||
if (!includeImplicit)
|
if (!includeImplicit)
|
||||||
{
|
{
|
||||||
|
@ -109,3 +109,61 @@ CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
ERROR: unsupported ON COMMIT and foreign key combination
|
ERROR: unsupported ON COMMIT and foreign key combination
|
||||||
DETAIL: Table "temptest4" references "temptest3" via foreign key constraint "temptest4_col_fkey", but they do not have the same ON COMMIT setting.
|
DETAIL: Table "temptest4" references "temptest3" via foreign key constraint "temptest4_col_fkey", 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;
|
||||||
|
@ -99,3 +99,36 @@ BEGIN;
|
|||||||
CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
|
CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
|
||||||
CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
|
CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
|
||||||
COMMIT;
|
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;
|
||||||
|
Loading…
Reference in New Issue
Block a user