From 1cdb1e604535e7e0e1c3a1d8c9d38d99c9d42ba3 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Fri, 11 Aug 2023 14:33:36 -0700
Subject: [PATCH v2] CREATE FUNCTION ... SEARCH FROM { DEFAULT | TRUSTED |
 SESSION }.

Declare how search_path will be initialized before execution of a
function or procedure. SEARCH FROM TRUSTED causes the search_path to
be initialized to a safe value that includes built-in objects. SEARCH
FROM SESSION causes the search_path to be inherited from the current
setting in the session (or current setting in the calling function or
procedure).

The SEARCH clause specifies a new function property that may be used
in the future to check for safe usage of functions in, e.g., index
expressions.

Discussion: https://postgr.es/m/2710f56add351a1ed553efb677408e51b060e67c.camel%40j-davis.com
---
 doc/src/sgml/ref/alter_function.sgml          | 15 +++++
 doc/src/sgml/ref/alter_procedure.sgml         | 15 +++++
 doc/src/sgml/ref/alter_routine.sgml           |  1 +
 doc/src/sgml/ref/create_function.sgml         | 55 +++++++++++++++
 doc/src/sgml/ref/create_procedure.sgml        | 55 +++++++++++++++
 src/backend/catalog/pg_aggregate.c            |  1 +
 src/backend/catalog/pg_proc.c                 |  2 +
 src/backend/commands/functioncmds.c           | 56 +++++++++++++++-
 src/backend/commands/typecmds.c               |  4 ++
 src/backend/optimizer/util/clauses.c          |  1 +
 src/backend/parser/gram.y                     | 15 +++++
 src/backend/utils/fmgr/fmgr.c                 | 23 +++++--
 src/bin/pg_dump/pg_dump.c                     | 18 ++++-
 src/bin/psql/describe.c                       | 23 ++++++-
 src/include/catalog/namespace.h               |  6 ++
 src/include/catalog/pg_proc.h                 | 13 ++++
 .../regress/expected/create_function_sql.out  | 67 +++++++++++++++++++
 src/test/regress/expected/psql.out            | 12 ++--
 src/test/regress/sql/create_function_sql.sql  | 44 ++++++++++++
 19 files changed, 407 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/alter_function.sgml b/doc/src/sgml/ref/alter_function.sgml
index 8193b17f25..b4103c0c90 100644
--- a/doc/src/sgml/ref/alter_function.sgml
+++ b/doc/src/sgml/ref/alter_function.sgml
@@ -37,6 +37,7 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
     CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
     IMMUTABLE | STABLE | VOLATILE
     [ NOT ] LEAKPROOF
+    SEARCH FROM { DEFAULT | TRUSTED | SESSION }
     [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
     PARALLEL { UNSAFE | RESTRICTED | SAFE }
     COST <replaceable class="parameter">execution_cost</replaceable>
@@ -198,6 +199,20 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SEARCH FROM DEFAULT</literal></term>
+    <term><literal>SEARCH FROM TRUSTED</literal></term>
+    <term><literal>SEARCH FROM SESSION</literal></term>
+
+    <listitem>
+      <para>
+       Change the <literal>SEARCH</literal> behavior of the function to the
+       specified setting.  See <xref linkend="sql-createfunction"/> for
+       details.
+      </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal><optional> EXTERNAL </optional> SECURITY INVOKER</literal></term>
     <term><literal><optional> EXTERNAL </optional> SECURITY DEFINER</literal></term>
diff --git a/doc/src/sgml/ref/alter_procedure.sgml b/doc/src/sgml/ref/alter_procedure.sgml
index a4737a3439..63bc6d695b 100644
--- a/doc/src/sgml/ref/alter_procedure.sgml
+++ b/doc/src/sgml/ref/alter_procedure.sgml
@@ -34,6 +34,7 @@ ALTER PROCEDURE <replaceable>name</replaceable> [ ( [ [ <replaceable class="para
 
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
+    SEARCH FROM { DEFAULT | TRUSTED | SESSION }
     [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
     SET <replaceable class="parameter">configuration_parameter</replaceable> { TO | = } { <replaceable class="parameter">value</replaceable> | DEFAULT }
     SET <replaceable class="parameter">configuration_parameter</replaceable> FROM CURRENT
@@ -158,6 +159,20 @@ ALTER PROCEDURE <replaceable>name</replaceable> [ ( [ [ <replaceable class="para
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SEARCH FROM DEFAULT</literal></term>
+    <term><literal>SEARCH FROM TRUSTED</literal></term>
+    <term><literal>SEARCH FROM SESSION</literal></term>
+
+    <listitem>
+      <para>
+       Change the <literal>SEARCH</literal> behavior of the function to the
+       specified setting.  See <xref linkend="sql-createprocedure"/> for
+       details.
+      </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal><optional> EXTERNAL </optional> SECURITY INVOKER</literal></term>
     <term><literal><optional> EXTERNAL </optional> SECURITY DEFINER</literal></term>
diff --git a/doc/src/sgml/ref/alter_routine.sgml b/doc/src/sgml/ref/alter_routine.sgml
index d6c9dea2eb..f8575b6cd0 100644
--- a/doc/src/sgml/ref/alter_routine.sgml
+++ b/doc/src/sgml/ref/alter_routine.sgml
@@ -36,6 +36,7 @@ ALTER ROUTINE <replaceable>name</replaceable> [ ( [ [ <replaceable class="parame
 
     IMMUTABLE | STABLE | VOLATILE
     [ NOT ] LEAKPROOF
+    SEARCH FROM { DEFAULT | TRUSTED | SESSION }
     [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
     PARALLEL { UNSAFE | RESTRICTED | SAFE }
     COST <replaceable class="parameter">execution_cost</replaceable>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 863d99d1fc..0909bd9c06 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -31,6 +31,7 @@ CREATE [ OR REPLACE ] FUNCTION
     | { IMMUTABLE | STABLE | VOLATILE }
     | [ NOT ] LEAKPROOF
     | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }
+    | SEARCH FROM { DEFAULT | TRUSTED | SESSION }
     | { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER }
     | PARALLEL { UNSAFE | RESTRICTED | SAFE }
     | COST <replaceable class="parameter">execution_cost</replaceable>
@@ -402,6 +403,60 @@ CREATE [ OR REPLACE ] FUNCTION
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>SEARCH FROM DEFAULT</literal></term>
+     <term><literal>SEARCH FROM TRUSTED</literal></term>
+     <term><literal>SEARCH FROM SESSION</literal></term>
+
+     <listitem>
+      <para>
+       Declare how <xref linkend="guc-search-path"/> will be initialized
+       before execution. <literal>SEARCH FROM TRUSTED</literal> causes the
+       search_path to be initialized to a safe value that includes built-in
+       objects. <literal>SEARCH FROM SESSION</literal> causes the search_path
+       to be inherited from the current setting in the session (or
+       current setting in the calling function or procedure).
+      </para>
+      <warning>
+       <para>
+        If <literal>search_path</literal> is inherited from the session, the
+        caller of the function or procedure may be able to manipulate its
+        behavior.
+       </para>
+      </warning>
+      <para>
+       Regardless of the <literal>SEARCH</literal> clause, the
+       <literal>search_path</literal> may be changed later just like any other
+       GUC, e.g. by using a <literal>SET</literal> clause in the declaration
+       or a <xref linkend="sql-set"/> command during execution.
+      </para>
+      <para>
+       If the function or procedure is specified with
+       <replaceable>sql_body</replaceable>, the <literal>SEARCH</literal>
+       clause does not affect the <literal>search_path</literal> used to find
+       objects referenced in the <replaceable>sql_body</replaceable> because
+       the it's parsed at definition time.
+      </para>
+      <para>
+       <literal>SEARCH FROM DEFAULT</literal> is the same as not specifying
+       any <literal>SEARCH</literal> clause at all, and the behavior is
+       equivalent to <literal>SEARCH FROM SESSION</literal>.
+      </para>
+      <note>
+       <para>
+        The <literal>search_path</literal> setting is important for functions
+        or procedures that parse statements at execution time (such as
+        PL/pgSQL, SQL specified with a <replaceable>definition</replaceable>,
+        or those that use the <link linkend="spi">Server Programming
+        Interface</link>), or those that call other functions or procedures
+        that parse statements at execution time.
+       </para>
+       <para>
+       </para>
+      </note>
+     </listitem>
+    </varlistentry>
+
    <varlistentry>
     <term><literal><optional>EXTERNAL</optional> SECURITY INVOKER</literal></term>
     <term><literal><optional>EXTERNAL</optional> SECURITY DEFINER</literal></term>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index 03a14c8684..c1c63f0527 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -25,6 +25,7 @@ CREATE [ OR REPLACE ] PROCEDURE
     <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [ { DEFAULT | = } <replaceable class="parameter">default_expr</replaceable> ] [, ...] ] )
   { LANGUAGE <replaceable class="parameter">lang_name</replaceable>
     | TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ]
+    | SEARCH FROM { DEFAULT | TRUSTED | SESSION }
     | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
     | SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
     | AS '<replaceable class="parameter">definition</replaceable>'
@@ -193,6 +194,60 @@ CREATE [ OR REPLACE ] PROCEDURE
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>SEARCH FROM DEFAULT</literal></term>
+     <term><literal>SEARCH FROM TRUSTED</literal></term>
+     <term><literal>SEARCH FROM SESSION</literal></term>
+
+     <listitem>
+      <para>
+       Declare how <xref linkend="guc-search-path"/> will be initialized
+       before execution. <literal>SEARCH FROM TRUSTED</literal> causes the
+       search_path to be initialized to a safe value that includes built-in
+       objects. <literal>SEARCH FROM SESSION</literal> causes the search_path
+       to be inherited from the current setting in the session (or
+       current setting in the calling function or procedure).
+      </para>
+      <warning>
+       <para>
+        If <literal>search_path</literal> is inherited from the session, the
+        caller of the function or procedure may be able to manipulate its
+        behavior.
+       </para>
+      </warning>
+      <para>
+       Regardless of the <literal>SEARCH</literal> clause, the
+       <literal>search_path</literal> may be changed later just like any other
+       GUC, e.g. by using a <literal>SET</literal> clause in the declaration
+       or a <xref linkend="sql-set"/> command during execution.
+      </para>
+      <para>
+       If the function or procedure is specified with
+       <replaceable>sql_body</replaceable>, the <literal>SEARCH</literal>
+       clause does not affect the <literal>search_path</literal> used to find
+       objects referenced in the <replaceable>sql_body</replaceable> because
+       the it's parsed at definition time.
+      </para>
+      <para>
+       <literal>SEARCH FROM DEFAULT</literal> is the same as not specifying
+       any <literal>SEARCH</literal> clause at all, and the behavior is
+       equivalent to <literal>SEARCH FROM SESSION</literal>.
+      </para>
+      <note>
+       <para>
+        The <literal>search_path</literal> setting is important for functions
+        or procedures that parse statements at execution time (such as
+        PL/pgSQL, SQL specified with a <replaceable>definition</replaceable>,
+        or those that use the <link linkend="spi">Server Programming
+        Interface</link>), or those that call other functions or procedures
+        that parse statements at execution time.
+       </para>
+       <para>
+       </para>
+      </note>
+     </listitem>
+    </varlistentry>
+
    <varlistentry>
     <term><literal><optional>EXTERNAL</optional> SECURITY INVOKER</literal></term>
     <term><literal><optional>EXTERNAL</optional> SECURITY DEFINER</literal></term>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index ebc4454743..e4ec68ec43 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -624,6 +624,7 @@ AggregateCreate(const char *aggName,
 							 NULL,	/* probin */
 							 NULL,	/* prosqlbody */
 							 PROKIND_AGGREGATE,
+							 PROSEARCH_DEFAULT, /* no SEARCH clause for aggregates */
 							 false, /* security invoker (currently not
 									 * definable for agg) */
 							 false, /* isLeakProof */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index b5fd364003..8a252acd99 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -81,6 +81,7 @@ ProcedureCreate(const char *procedureName,
 				const char *probin,
 				Node *prosqlbody,
 				char prokind,
+				char prosearch,
 				bool security_definer,
 				bool isLeakProof,
 				bool isStrict,
@@ -308,6 +309,7 @@ ProcedureCreate(const char *procedureName,
 	values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
 	values[Anum_pg_proc_prosupport - 1] = ObjectIdGetDatum(prosupport);
 	values[Anum_pg_proc_prokind - 1] = CharGetDatum(prokind);
+	values[Anum_pg_proc_prosearch - 1] = CharGetDatum(prosearch);
 	values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
 	values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
 	values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7ba6a86ebe..1f086b84a5 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -507,6 +507,7 @@ compute_common_attribute(ParseState *pstate,
 						 DefElem *defel,
 						 DefElem **volatility_item,
 						 DefElem **strict_item,
+						 DefElem **search_item,
 						 DefElem **security_item,
 						 DefElem **leakproof_item,
 						 List **set_items,
@@ -533,6 +534,13 @@ compute_common_attribute(ParseState *pstate,
 
 		*strict_item = defel;
 	}
+	else if (strcmp(defel->defname, "search") == 0)
+	{
+		if (*search_item)
+			errorConflictingDefElem(defel, pstate);
+
+		*search_item = defel;
+	}
 	else if (strcmp(defel->defname, "security") == 0)
 	{
 		if (*security_item)
@@ -603,6 +611,24 @@ procedure_error:
 	return false;
 }
 
+static char
+interpret_func_search(DefElem *defel)
+{
+	char	   *str = strVal(defel->arg);
+
+	if (strcmp(str, "default") == 0)
+		return PROSEARCH_DEFAULT;
+	else if (strcmp(str, "trusted") == 0)
+		return PROSEARCH_TRUSTED;
+	else if (strcmp(str, "session") == 0)
+		return PROSEARCH_SESSION;
+	else
+	{
+		elog(ERROR, "invalid search \"%s\"", str);
+		return 0;				/* keep compiler quiet */
+	}
+}
+
 static char
 interpret_func_volatility(DefElem *defel)
 {
@@ -725,6 +751,7 @@ compute_function_attributes(ParseState *pstate,
 							bool *windowfunc_p,
 							char *volatility_p,
 							bool *strict_p,
+							char *search_p,
 							bool *security_definer,
 							bool *leakproof_p,
 							ArrayType **proconfig,
@@ -740,6 +767,7 @@ compute_function_attributes(ParseState *pstate,
 	DefElem    *windowfunc_item = NULL;
 	DefElem    *volatility_item = NULL;
 	DefElem    *strict_item = NULL;
+	DefElem    *search_item = NULL;
 	DefElem    *security_item = NULL;
 	DefElem    *leakproof_item = NULL;
 	List	   *set_items = NIL;
@@ -786,6 +814,7 @@ compute_function_attributes(ParseState *pstate,
 										  defel,
 										  &volatility_item,
 										  &strict_item,
+										  &search_item,
 										  &security_item,
 										  &leakproof_item,
 										  &set_items,
@@ -814,6 +843,8 @@ compute_function_attributes(ParseState *pstate,
 		*volatility_p = interpret_func_volatility(volatility_item);
 	if (strict_item)
 		*strict_p = boolVal(strict_item->arg);
+	if (search_item)
+		*search_p = interpret_func_search(search_item);
 	if (security_item)
 		*security_definer = boolVal(security_item->arg);
 	if (leakproof_item)
@@ -1042,7 +1073,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 				isStrict,
 				security,
 				isLeakProof;
-	char		volatility;
+	char		prosearch,
+				volatility;
 	ArrayType  *proconfig;
 	float4		procost;
 	float4		prorows;
@@ -1067,6 +1099,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	language = NULL;
 	isWindowFunc = false;
 	isStrict = false;
+	prosearch = PROSEARCH_DEFAULT;
 	security = false;
 	isLeakProof = false;
 	volatility = PROVOLATILE_VOLATILE;
@@ -1082,10 +1115,16 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 								stmt->options,
 								&as_clause, &language, &transformDefElem,
 								&isWindowFunc, &volatility,
-								&isStrict, &security, &isLeakProof,
-								&proconfig, &procost, &prorows,
+								&isStrict, &prosearch, &security,
+								&isLeakProof, &proconfig, &procost, &prorows,
 								&prosupport, &parallel);
 
+	if (volatility == PROVOLATILE_IMMUTABLE && prosearch == PROSEARCH_SESSION)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("immutable functions cannot be specified with SEARCH FROM SESSION"),
+				 errhint("Specify SEARCH FROM TRUSTED instead.")));
+
 	if (!language)
 	{
 		if (stmt->sql_body)
@@ -1271,6 +1310,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 						   probin_str,	/* converted to text later */
 						   prosqlbody,
 						   stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
+						   prosearch,
 						   security,
 						   isLeakProof,
 						   isStrict,
@@ -1355,6 +1395,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 	ListCell   *l;
 	DefElem    *volatility_item = NULL;
 	DefElem    *strict_item = NULL;
+	DefElem    *search_item = NULL;
 	DefElem    *security_def_item = NULL;
 	DefElem    *leakproof_item = NULL;
 	List	   *set_items = NIL;
@@ -1399,6 +1440,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 									 defel,
 									 &volatility_item,
 									 &strict_item,
+									 &search_item,
 									 &security_def_item,
 									 &leakproof_item,
 									 &set_items,
@@ -1413,8 +1455,16 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 		procForm->provolatile = interpret_func_volatility(volatility_item);
 	if (strict_item)
 		procForm->proisstrict = boolVal(strict_item->arg);
+	if (search_item)
+		procForm->prosearch = interpret_func_search(search_item);
 	if (security_def_item)
 		procForm->prosecdef = boolVal(security_def_item->arg);
+	if (procForm->provolatile == PROVOLATILE_IMMUTABLE &&
+		procForm->prosearch == PROSEARCH_SESSION)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("immutable functions cannot be specified with SEARCH FROM SESSION"),
+				 errhint("Specify SEARCH FROM TRUSTED instead.")));
 	if (leakproof_item)
 	{
 		procForm->proleakproof = boolVal(leakproof_item->arg);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 5e97606793..baa8dd706e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1763,6 +1763,7 @@ makeRangeConstructors(const char *name, Oid namespace,
 								 NULL,	/* probin */
 								 NULL,	/* prosqlbody */
 								 PROKIND_FUNCTION,
+								 PROSEARCH_DEFAULT,
 								 false, /* security_definer */
 								 false, /* leakproof */
 								 false, /* isStrict */
@@ -1828,6 +1829,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 							 NULL,	/* probin */
 							 NULL,	/* prosqlbody */
 							 PROKIND_FUNCTION,
+							 PROSEARCH_DEFAULT,
 							 false, /* security_definer */
 							 false, /* leakproof */
 							 true,	/* isStrict */
@@ -1872,6 +1874,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 							 NULL,	/* probin */
 							 NULL,	/* prosqlbody */
 							 PROKIND_FUNCTION,
+							 PROSEARCH_DEFAULT,
 							 false, /* security_definer */
 							 false, /* leakproof */
 							 true,	/* isStrict */
@@ -1910,6 +1913,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 							 NULL,	/* probin */
 							 NULL,	/* prosqlbody */
 							 PROKIND_FUNCTION,
+							 PROSEARCH_DEFAULT,
 							 false, /* security_definer */
 							 false, /* leakproof */
 							 true,	/* isStrict */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index da258968b8..5a843d71ae 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4474,6 +4474,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 	 */
 	if (funcform->prolang != SQLlanguageId ||
 		funcform->prokind != PROKIND_FUNCTION ||
+		funcform->prosearch == PROSEARCH_TRUSTED ||
 		funcform->prosecdef ||
 		funcform->proretset ||
 		funcform->prorettype == RECORDOID ||
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d2032885e..ad6d09cc47 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8545,6 +8545,21 @@ common_func_opt_item:
 				{
 					$$ = makeDefElem("support", (Node *) $2, @1);
 				}
+			| SEARCH FROM DEFAULT
+				{
+					/* we abuse the normal content of a DefElem here */
+					$$ = makeDefElem("search", (Node *) makeString("default"), @1);
+				}
+			| SEARCH FROM TRUSTED
+				{
+					/* we abuse the normal content of a DefElem here */
+					$$ = makeDefElem("search", (Node *) makeString("trusted"), @1);
+				}
+			| SEARCH FROM SESSION
+				{
+					/* we abuse the normal content of a DefElem here */
+					$$ = makeDefElem("search", (Node *) makeString("session"), @1);
+				}
 			| FunctionSetResetClause
 				{
 					/* we abuse the normal content of a DefElem here */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 9dfdf890c5..d65d88fc31 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
@@ -203,6 +204,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
+		 procedureStruct->prosearch == PROSEARCH_TRUSTED ||
 		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
 		 FmgrHookIsNeeded(functionId)))
 	{
@@ -612,6 +614,7 @@ struct fmgr_security_definer_cache
 {
 	FmgrInfo	flinfo;			/* lookup info for target function */
 	Oid			userid;			/* userid to set, or InvalidOid */
+	char	   *searchPath;		/* from SEARCH clause, if specified */
 	List	   *configNames;	/* GUC names to set, or NIL */
 	List	   *configValues;	/* GUC values to set, or NIL */
 	Datum		arg;			/* passthrough argument for plugin modules */
@@ -630,6 +633,9 @@ struct fmgr_security_definer_cache
 extern Datum
 fmgr_security_definer(PG_FUNCTION_ARGS)
 {
+	GucContext	context = superuser() ? PGC_SUSET : PGC_USERSET;
+	GucSource	source = PGC_S_SESSION;
+	GucAction	action = GUC_ACTION_SAVE;
 	Datum		result;
 	struct fmgr_security_definer_cache *volatile fcache;
 	FmgrInfo   *save_flinfo;
@@ -662,6 +668,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 				 fcinfo->flinfo->fn_oid);
 		procedureStruct = (Form_pg_proc) GETSTRUCT(tuple);
 
+		if (procedureStruct->prosearch == PROSEARCH_TRUSTED)
+			fcache->searchPath = NAMESPACE_TRUSTED_SEARCH_PATH;
+
 		if (procedureStruct->prosecdef)
 			fcache->userid = procedureStruct->proowner;
 
@@ -687,20 +696,22 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
 	/* GetUserIdAndSecContext is cheap enough that no harm in a wasted call */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	if (fcache->configNames != NIL) /* Need a new GUC nesting level */
+	if (fcache->searchPath != NULL || fcache->configNames != NIL) /* Need a new GUC nesting level */
 		save_nestlevel = NewGUCNestLevel();
 	else
-		save_nestlevel = 0;		/* keep compiler quiet */
+		save_nestlevel = 0;
 
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(fcache->userid,
 							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	if (fcache->searchPath != NULL)
+		(void) set_config_option("search_path", fcache->searchPath,
+								 context, source,
+								 action, true, 0, false);
+
 	forboth(lc1, fcache->configNames, lc2, fcache->configValues)
 	{
-		GucContext	context = superuser() ? PGC_SUSET : PGC_USERSET;
-		GucSource	source = PGC_S_SESSION;
-		GucAction	action = GUC_ACTION_SAVE;
 		char	   *name = lfirst(lc1);
 		char	   *value = lfirst(lc2);
 
@@ -749,7 +760,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
 	fcinfo->flinfo = save_flinfo;
 
-	if (fcache->configNames != NIL)
+	if (save_nestlevel > 0)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(save_userid, save_sec_context);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f7b6176692..184508052f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12005,6 +12005,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 	char	   *prokind;
 	char	   *provolatile;
 	char	   *proisstrict;
+	char	   *prosearch;
 	char	   *prosecdef;
 	char	   *proleakproof;
 	char	   *proconfig;
@@ -12079,10 +12080,17 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 
 		if (fout->remoteVersion >= 140000)
 			appendPQExpBufferStr(query,
-								 "pg_get_function_sqlbody(p.oid) AS prosqlbody\n");
+								 "pg_get_function_sqlbody(p.oid) AS prosqlbody,\n");
 		else
 			appendPQExpBufferStr(query,
-								 "NULL AS prosqlbody\n");
+								 "NULL AS prosqlbody,\n");
+
+		if (fout->remoteVersion >= 170000)
+			appendPQExpBufferStr(query,
+								 "prosearch\n");
+		else
+			appendPQExpBufferStr(query,
+								 "NULL AS prosearch\n");
 
 		appendPQExpBufferStr(query,
 							 "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
@@ -12120,6 +12128,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 	prokind = PQgetvalue(res, 0, PQfnumber(res, "prokind"));
 	provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
 	proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
+	prosearch = PQgetvalue(res, 0, PQfnumber(res, "prosearch"));
 	prosecdef = PQgetvalue(res, 0, PQfnumber(res, "prosecdef"));
 	proleakproof = PQgetvalue(res, 0, PQfnumber(res, "proleakproof"));
 	proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
@@ -12245,6 +12254,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 	if (proisstrict[0] == 't')
 		appendPQExpBufferStr(q, " STRICT");
 
+	if (prosearch[0] == 't')
+		appendPQExpBufferStr(q, " SEARCH FROM TRUSTED");
+	else if (prosearch[0] == 's')
+		appendPQExpBufferStr(q, " SEARCH FROM SESSION");
+
 	if (prosecdef[0] == 't')
 		appendPQExpBufferStr(q, " SECURITY DEFINER");
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bac94a338c..a2b950382b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -301,7 +301,10 @@ describeFunctions(const char *functypes, const char *func_pattern,
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, true, true, true, true, false, true, false, false, false, false};
+
+	/* No "Search" column before 17 */
+	static const bool translate_columns_pre_17[] = {false, false, false, false, true, true, true, false, true, false, false, false, false};
 
 	/* No "Parallel" column before 9.6 */
 	static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
@@ -377,6 +380,17 @@ describeFunctions(const char *functypes, const char *func_pattern,
 
 	if (verbose)
 	{
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ",\n CASE\n"
+							  "  WHEN p.prosearch = 'd' THEN '%s'\n"
+							  "  WHEN p.prosearch = 't' THEN '%s'\n"
+							  "  WHEN p.prosearch = 's' THEN '%s'\n"
+							  " END as \"%s\"",
+							  gettext_noop("default"),
+							  gettext_noop("trusted"),
+							  gettext_noop("session"),
+							  gettext_noop("Search"));
 		appendPQExpBuffer(&buf,
 						  ",\n CASE\n"
 						  "  WHEN p.provolatile = 'i' THEN '%s'\n"
@@ -588,11 +602,16 @@ describeFunctions(const char *functypes, const char *func_pattern,
 	myopt.nullPrint = NULL;
 	myopt.title = _("List of functions");
 	myopt.translate_header = true;
-	if (pset.sversion >= 90600)
+	if (pset.sversion >= 170000)
 	{
 		myopt.translate_columns = translate_columns;
 		myopt.n_translate_columns = lengthof(translate_columns);
 	}
+	else if (pset.sversion >= 90600)
+	{
+		myopt.translate_columns = translate_columns_pre_17;
+		myopt.n_translate_columns = lengthof(translate_columns_pre_17);
+	}
 	else
 	{
 		myopt.translate_columns = translate_columns_pre_96;
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index e027940430..c1c313eec6 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -76,6 +76,12 @@ typedef enum RVROption
 typedef void (*RangeVarGetRelidCallback) (const RangeVar *relation, Oid relId,
 										  Oid oldRelId, void *callback_arg);
 
+/*
+ * Trusted search_path for cases where relying on the session search_path is
+ * unsafe (e.g. for SECURITY DEFINER functions).
+ */
+#define NAMESPACE_TRUSTED_SEARCH_PATH		"pg_catalog, pg_temp"
+
 #define RangeVarGetRelid(relation, lockmode, missing_ok) \
 	RangeVarGetRelidExtended(relation, lockmode, \
 							 (missing_ok) ? RVR_MISSING_OK : 0, NULL, NULL)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index fdb39d4001..9b3763cc1b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -58,6 +58,9 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
 	/* see PROKIND_ categories below */
 	char		prokind BKI_DEFAULT(f);
 
+	/* security definer */
+	char		prosearch BKI_DEFAULT(d);
+
 	/* security definer */
 	bool		prosecdef BKI_DEFAULT(f);
 
@@ -150,6 +153,15 @@ DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, ProcedureNameArgsNspI
 #define PROKIND_WINDOW 'w'
 #define PROKIND_PROCEDURE 'p'
 
+/*
+ * Symbolic values for prosearch column: these determine how search_path is
+ * initialized when executing the function. PROSEARCH_DEFAULT means that no
+ * SEARCH clause was specified.
+ */
+#define PROSEARCH_DEFAULT		'd'
+#define PROSEARCH_TRUSTED		't'
+#define PROSEARCH_SESSION		's'
+
 /*
  * Symbolic values for provolatile column: these indicate whether the result
  * of a function is dependent *only* on the values of its explicit arguments,
@@ -197,6 +209,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
 									 const char *probin,
 									 Node *prosqlbody,
 									 char prokind,
+									 char prosearch,
 									 bool security_definer,
 									 bool isLeakProof,
 									 bool isStrict,
diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out
index 50aca5940f..83c649f725 100644
--- a/src/test/regress/expected/create_function_sql.out
+++ b/src/test/regress/expected/create_function_sql.out
@@ -88,6 +88,73 @@ SELECT proname, provolatile FROM pg_proc
  functest_b_4 | v
 (4 rows)
 
+--
+-- SEARCH FROM DEFAULT | TRUSTED | SESSION
+--
+CREATE FUNCTION f_show_search_path() RETURNS TEXT
+  LANGUAGE plpgsql AS
+$$
+  BEGIN
+    RETURN current_setting('search_path');
+  END;
+$$;
+CREATE PROCEDURE p_show_search_path()
+  LANGUAGE plpgsql AS
+$$
+  BEGIN
+    RAISE NOTICE 'search_path: %', current_setting('search_path');
+  END;
+$$;
+SELECT f_show_search_path();
+   f_show_search_path   
+------------------------
+ temp_func_test, public
+(1 row)
+
+CALL p_show_search_path();
+NOTICE:  search_path: temp_func_test, public
+ALTER FUNCTION f_show_search_path() SEARCH FROM TRUSTED;
+SELECT f_show_search_path();
+ f_show_search_path  
+---------------------
+ pg_catalog, pg_temp
+(1 row)
+
+ALTER FUNCTION f_show_search_path() SEARCH FROM DEFAULT;
+SELECT f_show_search_path();
+   f_show_search_path   
+------------------------
+ temp_func_test, public
+(1 row)
+
+ALTER FUNCTION f_show_search_path() SEARCH FROM SESSION;
+SELECT f_show_search_path();
+   f_show_search_path   
+------------------------
+ temp_func_test, public
+(1 row)
+
+ALTER FUNCTION f_show_search_path() IMMUTABLE; -- fail
+ERROR:  immutable functions cannot be specified with SEARCH FROM SESSION
+HINT:  Specify SEARCH FROM TRUSTED instead.
+ALTER FUNCTION f_show_search_path() SEARCH FROM DEFAULT;
+ALTER FUNCTION f_show_search_path() IMMUTABLE;
+ALTER FUNCTION f_show_search_path() SEARCH FROM SESSION; -- fail
+ERROR:  immutable functions cannot be specified with SEARCH FROM SESSION
+HINT:  Specify SEARCH FROM TRUSTED instead.
+ALTER FUNCTION f_show_search_path() VOLATILE;
+ALTER ROUTINE p_show_search_path() SEARCH FROM TRUSTED SET search_path = test1;
+CALL p_show_search_path();
+NOTICE:  search_path: test1
+ALTER ROUTINE f_show_search_path() SEARCH FROM SESSION SET search_path = test1;
+SELECT f_show_search_path();
+ f_show_search_path 
+--------------------
+ test1
+(1 row)
+
+DROP FUNCTION f_show_search_path();
+DROP PROCEDURE p_show_search_path();
 --
 -- SECURITY DEFINER | INVOKER
 --
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7cd0c27cca..d6293c4003 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5277,12 +5277,12 @@ create function psql_df_plpgsql ()
   as $$ begin return; end; $$;
 comment on function psql_df_plpgsql () is 'some comment';
 \df+ psql_df_*
-                                                                                       List of functions
- Schema |       Name       | Result data type | Argument data types | Type | Volatility | Parallel |       Owner       | Security | Access privileges | Language | Internal name | Description  
---------+------------------+------------------+---------------------+------+------------+----------+-------------------+----------+-------------------+----------+---------------+--------------
- public | psql_df_internal | double precision | double precision    | func | immutable  | safe     | regress_psql_user | invoker  |                   | internal | dsin          | 
- public | psql_df_plpgsql  | void             |                     | func | volatile   | unsafe   | regress_psql_user | invoker  |                   | plpgsql  |               | some comment
- public | psql_df_sql      | integer          | x integer           | func | volatile   | unsafe   | regress_psql_user | definer  |                   | sql      |               | 
+                                                                                            List of functions
+ Schema |       Name       | Result data type | Argument data types | Type | Search  | Volatility | Parallel |       Owner       | Security | Access privileges | Language | Internal name | Description  
+--------+------------------+------------------+---------------------+------+---------+------------+----------+-------------------+----------+-------------------+----------+---------------+--------------
+ public | psql_df_internal | double precision | double precision    | func | default | immutable  | safe     | regress_psql_user | invoker  |                   | internal | dsin          | 
+ public | psql_df_plpgsql  | void             |                     | func | default | volatile   | unsafe   | regress_psql_user | invoker  |                   | plpgsql  |               | some comment
+ public | psql_df_sql      | integer          | x integer           | func | default | volatile   | unsafe   | regress_psql_user | definer  |                   | sql      |               | 
 (3 rows)
 
 rollback;
diff --git a/src/test/regress/sql/create_function_sql.sql b/src/test/regress/sql/create_function_sql.sql
index 89e9af3a49..383633cec9 100644
--- a/src/test/regress/sql/create_function_sql.sql
+++ b/src/test/regress/sql/create_function_sql.sql
@@ -60,6 +60,50 @@ SELECT proname, provolatile FROM pg_proc
                      'functest_B_3'::regproc,
 		     'functest_B_4'::regproc) ORDER BY proname;
 
+--
+-- SEARCH FROM DEFAULT | TRUSTED | SESSION
+--
+
+CREATE FUNCTION f_show_search_path() RETURNS TEXT
+  LANGUAGE plpgsql AS
+$$
+  BEGIN
+    RETURN current_setting('search_path');
+  END;
+$$;
+
+CREATE PROCEDURE p_show_search_path()
+  LANGUAGE plpgsql AS
+$$
+  BEGIN
+    RAISE NOTICE 'search_path: %', current_setting('search_path');
+  END;
+$$;
+
+SELECT f_show_search_path();
+CALL p_show_search_path();
+
+ALTER FUNCTION f_show_search_path() SEARCH FROM TRUSTED;
+SELECT f_show_search_path();
+ALTER FUNCTION f_show_search_path() SEARCH FROM DEFAULT;
+SELECT f_show_search_path();
+ALTER FUNCTION f_show_search_path() SEARCH FROM SESSION;
+SELECT f_show_search_path();
+
+ALTER FUNCTION f_show_search_path() IMMUTABLE; -- fail
+ALTER FUNCTION f_show_search_path() SEARCH FROM DEFAULT;
+ALTER FUNCTION f_show_search_path() IMMUTABLE;
+ALTER FUNCTION f_show_search_path() SEARCH FROM SESSION; -- fail
+ALTER FUNCTION f_show_search_path() VOLATILE;
+
+ALTER ROUTINE p_show_search_path() SEARCH FROM TRUSTED SET search_path = test1;
+CALL p_show_search_path();
+ALTER ROUTINE f_show_search_path() SEARCH FROM SESSION SET search_path = test1;
+SELECT f_show_search_path();
+
+DROP FUNCTION f_show_search_path();
+DROP PROCEDURE p_show_search_path();
+
 --
 -- SECURITY DEFINER | INVOKER
 --
-- 
2.34.1

