Pavel Stehule писал(а) 2024-12-31 18:39:
Hi
út 31. 12. 2024 v 16:36 odesílatel Alexander Pyhalov
<a.pyha...@postgrespro.ru> napsal:
Hi.
What should we do with "pre-parsed" SQL functions (when prosrc is
empty)? How should we create cached plans when we don't have raw
parsetrees?
Currently we can create cached plans without raw parsetrees, but
this
means that plan revalidation doesn't work, choose_custom_plan()
always returns false and we get generic plan. Perhaps, we need
some
form
of GetCachedPlan(), which ignores raw_parse_tree?
I don't think you need a new form of GetCachedPlan(). Instead, it
seems that StmtPlanRequiresRevalidation() should be revised. As I
got
from comments and the d8b2fcc9d4 commit message, the primary goal
was
to skip revalidation of utility statements. Skipping revalidation
was
a positive side effect, as long as we didn't support custom plans
for
them anyway. But as you're going to change this,
StmtPlanRequiresRevalidation() needs to be revised.
Thanks for feedback.
I've modifed StmtPlanRequiresRevalidation() so that it looks on
queries
command type.
Not sure if it's enough or I have to recreate something more similar
to
stmt_requires_parse_analysis()
logic. I was wondering if this can lead to triggering plan
revalidation
in RevalidateCachedQuery().
I suppose not - as we plan in executor (so shouldn't catch userid
change
or see some changes in
related objects. Revalidation would kill our plan, destroying
resultDesc.
Also while looking at this, fixed processing of instead of rules
(which
would lead to NULL execution_state).
--
there are lot of fails found by tester
Please, can you check it?
Hi. Sorry for late response - we had holidays here and later there was
some urgent work from 2024 :)
Do you speak about failures on check-world?
It seems
commit a8ccf4e93a7eeaae66007bbf78cf9183ceb1b371
Author: Richard Guo <r...@postgresql.org>
Date: Tue Nov 26 09:25:18 2024 +0900
Reordering DISTINCT keys to match input path's pathkeys
added new GUC enable_distinct_reordering and this caused test failures.
I've rebased patch on master. Tests pass here.
--
Best regards,
Alexander Pyhalov,
Postgres Professional
From 83674406fbf305f3d4827c6a2ec7aac0a1fcaedd Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyha...@postgrespro.ru>
Date: Fri, 23 Aug 2024 15:48:38 +0300
Subject: [PATCH] Use custom plan machinery for SQL functions
---
contrib/citext/expected/citext.out | 2 +-
contrib/citext/expected/citext_1.out | 2 +-
src/backend/executor/functions.c | 334 ++++++++++++++----
src/backend/utils/cache/plancache.c | 44 ++-
src/backend/utils/misc/guc_tables.c | 12 +
src/include/executor/functions.h | 2 +
src/test/regress/expected/numeric.out | 4 +-
src/test/regress/expected/plpgsql.out | 2 +-
src/test/regress/expected/rules.out | 35 ++
src/test/regress/expected/select_parallel.out | 4 +-
src/test/regress/expected/strings.out | 2 +-
src/test/regress/expected/sysviews.out | 3 +-
src/test/regress/sql/rules.sql | 24 ++
13 files changed, 385 insertions(+), 85 deletions(-)
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..f8765053651 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -1816,7 +1816,7 @@ SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citex
SELECT regexp_match('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g') AS "error";
ERROR: regexp_match() does not support the "global" option
HINT: Use the regexp_matches function instead.
-CONTEXT: SQL function "regexp_match" statement 1
+CONTEXT: SQL function "regexp_match" during startup
SELECT regexp_matches('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t;
t
---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..356dc6bba43 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -1816,7 +1816,7 @@ SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citex
SELECT regexp_match('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g') AS "error";
ERROR: regexp_match() does not support the "global" option
HINT: Use the regexp_matches function instead.
-CONTEXT: SQL function "regexp_match" statement 1
+CONTEXT: SQL function "regexp_match" during startup
SELECT regexp_matches('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t;
t
---
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 757f8068e21..4591c3894ca 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -33,9 +33,14 @@
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/plancache.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+/*
+ * GUC variables
+ */
+bool enable_sql_func_custom_plans = true;
/*
* Specialized DestReceiver for collecting query output in a SQL function
@@ -112,6 +117,11 @@ typedef struct
JunkFilter *junkFilter; /* will be NULL if function returns VOID */
+ /* Cached plans support */
+ List *queryTree_list; /* list of query lists */
+ List *plansource_list; /* list of plansource */
+ List *cplan_list; /* list of cached plans */
+
/*
* func_state is a List of execution_state records, each of which is the
* first for its original parsetree, with any additional records chained
@@ -454,6 +464,40 @@ sql_fn_resolve_param_name(SQLFunctionParseInfoPtr pinfo,
return NULL;
}
+/* Precheck command for validity in a function */
+static void
+check_planned_stmt(PlannedStmt *stmt, SQLFunctionCachePtr fcache)
+{
+
+ /*
+ * Precheck all commands for validity in a function. This should
+ * generally match the restrictions spi.c applies.
+ */
+ if (stmt->commandType == CMD_UTILITY)
+ {
+ if (IsA(stmt->utilityStmt, CopyStmt) &&
+ ((CopyStmt *) stmt->utilityStmt)->filename == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot COPY to/from client in an SQL function")));
+
+ if (IsA(stmt->utilityStmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in an SQL function",
+ CreateCommandName(stmt->utilityStmt))));
+ }
+
+ if (fcache->readonly_func && !CommandIsReadOnly(stmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a non-volatile function",
+ CreateCommandName((Node *) stmt))));
+
+}
+
/*
* Set up the per-query execution_state records for a SQL function.
*
@@ -466,85 +510,117 @@ init_execution_state(List *queryTree_list,
bool lazyEvalOK)
{
List *eslist = NIL;
+ List *cplan_list = NIL;
execution_state *lasttages = NULL;
ListCell *lc1;
foreach(lc1, queryTree_list)
{
- List *qtlist = lfirst_node(List, lc1);
+ List *qtlist;
execution_state *firstes = NULL;
execution_state *preves = NULL;
ListCell *lc2;
- foreach(lc2, qtlist)
+ if (fcache->plansource_list)
{
- Query *queryTree = lfirst_node(Query, lc2);
- PlannedStmt *stmt;
- execution_state *newes;
+ CachedPlan *cplan;
+ CachedPlanSource *plansource;
+ int cur_idx;
- /* Plan the query if needed */
- if (queryTree->commandType == CMD_UTILITY)
- {
- /* Utility commands require no planning. */
- stmt = makeNode(PlannedStmt);
- stmt->commandType = CMD_UTILITY;
- stmt->canSetTag = queryTree->canSetTag;
- stmt->utilityStmt = queryTree->utilityStmt;
- stmt->stmt_location = queryTree->stmt_location;
- stmt->stmt_len = queryTree->stmt_len;
- stmt->queryId = queryTree->queryId;
- }
- else
- stmt = pg_plan_query(queryTree,
- fcache->src,
- CURSOR_OPT_PARALLEL_OK,
- NULL);
+ /* Find plan source, corresponding to this query list */
+ cur_idx = foreach_current_index(lc1);
+ plansource = list_nth(fcache->plansource_list, cur_idx);
/*
- * Precheck all commands for validity in a function. This should
- * generally match the restrictions spi.c applies.
+ * Get plan for the query. If paramLI is set, we can get custom
+ * plan
*/
- if (stmt->commandType == CMD_UTILITY)
+ cplan = GetCachedPlan(plansource, fcache->paramLI, NULL, NULL);
+
+ /* Record cplan in plan list to be released on replanning */
+ cplan_list = lappend(cplan_list, cplan);
+
+ /* For each planned statement create execution state */
+ foreach(lc2, cplan->stmt_list)
{
- if (IsA(stmt->utilityStmt, CopyStmt) &&
- ((CopyStmt *) stmt->utilityStmt)->filename == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot COPY to/from client in an SQL function")));
+ PlannedStmt *stmt = lfirst(lc2);
+ execution_state *newes;
- if (IsA(stmt->utilityStmt, TransactionStmt))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- /* translator: %s is a SQL statement name */
- errmsg("%s is not allowed in an SQL function",
- CreateCommandName(stmt->utilityStmt))));
+ /* Check that stmt is valid for SQL function */
+ check_planned_stmt(stmt, fcache);
+
+ newes = (execution_state *) palloc(sizeof(execution_state));
+
+ if (preves)
+ preves->next = newes;
+ else
+ firstes = newes;
+
+ newes->next = NULL;
+ newes->status = F_EXEC_START;
+ newes->setsResult = false; /* might change below */
+ newes->lazyEval = false; /* might change below */
+ newes->stmt = stmt;
+ newes->qd = NULL;
+
+ if (stmt->canSetTag)
+ lasttages = newes;
+
+ preves = newes;
}
+ }
+ else
+ {
+ qtlist = lfirst_node(List, lc1);
- if (fcache->readonly_func && !CommandIsReadOnly(stmt))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- /* translator: %s is a SQL statement name */
- errmsg("%s is not allowed in a non-volatile function",
- CreateCommandName((Node *) stmt))));
-
- /* OK, build the execution_state for this query */
- newes = (execution_state *) palloc(sizeof(execution_state));
- if (preves)
- preves->next = newes;
- else
- firstes = newes;
+ foreach(lc2, qtlist)
+ {
+ Query *queryTree = lfirst_node(Query, lc2);
+ PlannedStmt *stmt;
+ execution_state *newes;
+
+ /* Plan the query if needed */
+ if (queryTree->commandType == CMD_UTILITY)
+ {
+ /* Utility commands require no planning. */
+ stmt = makeNode(PlannedStmt);
+ stmt->commandType = CMD_UTILITY;
+ stmt->canSetTag = queryTree->canSetTag;
+ stmt->utilityStmt = queryTree->utilityStmt;
+ stmt->stmt_location = queryTree->stmt_location;
+ stmt->stmt_len = queryTree->stmt_len;
+ stmt->queryId = queryTree->queryId;
+ }
+ else
+ {
+ /* Get generic plan for the query */
+ stmt = pg_plan_query(queryTree,
+ fcache->src,
+ CURSOR_OPT_PARALLEL_OK,
+ NULL);
+ }
+
+ /* Check that stmt is valid for SQL function */
+ check_planned_stmt(stmt, fcache);
- newes->next = NULL;
- newes->status = F_EXEC_START;
- newes->setsResult = false; /* might change below */
- newes->lazyEval = false; /* might change below */
- newes->stmt = stmt;
- newes->qd = NULL;
+ newes = (execution_state *) palloc(sizeof(execution_state));
+ if (preves)
+ preves->next = newes;
+ else
+ firstes = newes;
+
+ newes->next = NULL;
+ newes->status = F_EXEC_START;
+ newes->setsResult = false; /* might change below */
+ newes->lazyEval = false; /* might change below */
+ newes->stmt = stmt;
+ newes->qd = NULL;
- if (queryTree->canSetTag)
- lasttages = newes;
+ if (queryTree->canSetTag)
+ lasttages = newes;
- preves = newes;
+ preves = newes;
+ }
}
eslist = lappend(eslist, firstes);
@@ -573,6 +649,7 @@ init_execution_state(List *queryTree_list,
fcache->lazyEval = lasttages->lazyEval = true;
}
+ fcache->cplan_list = cplan_list;
return eslist;
}
@@ -596,6 +673,7 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
ListCell *lc;
Datum tmp;
bool isNull;
+ List *plansource_list;
/*
* Create memory context that holds all the SQLFunctionCache data. It
@@ -680,6 +758,7 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* plancache.c.
*/
queryTree_list = NIL;
+ plansource_list = NIL;
if (!isNull)
{
Node *n;
@@ -695,8 +774,16 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
{
Query *parsetree = lfirst_node(Query, lc);
List *queryTree_sublist;
+ CachedPlanSource *plansource;
AcquireRewriteLocks(parsetree, true, false);
+
+ if (enable_sql_func_custom_plans)
+ {
+ plansource = CreateCachedPlan(NULL, fcache->src, CreateCommandTag((Node *)parsetree));
+ plansource_list = lappend(plansource_list, plansource);
+ }
+
queryTree_sublist = pg_rewrite_query(parsetree);
queryTree_list = lappend(queryTree_list, queryTree_sublist);
}
@@ -711,6 +798,13 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
{
RawStmt *parsetree = lfirst_node(RawStmt, lc);
List *queryTree_sublist;
+ CachedPlanSource *plansource;
+
+ if (enable_sql_func_custom_plans)
+ {
+ plansource = CreateCachedPlan(parsetree, fcache->src, CreateCommandTag(parsetree->stmt));
+ plansource_list = lappend(plansource_list, plansource);
+ }
queryTree_sublist = pg_analyze_and_rewrite_withcb(parsetree,
fcache->src,
@@ -751,6 +845,34 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
false,
&resulttlist);
+ /*
+ * Queries could be rewritten by check_sql_fn_retval(). Now when they have
+ * their final form, we can complete plan cache entry creation.
+ */
+ if (plansource_list != NIL)
+ {
+ ListCell *qlc;
+ ListCell *plc;
+
+ forboth(qlc, queryTree_list, plc, plansource_list)
+ {
+ List *queryTree_sublist = lfirst(qlc);
+ CachedPlanSource *plansource = lfirst(plc);
+
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ queryTree_sublist,
+ NULL,
+ NULL,
+ 0,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ CURSOR_OPT_PARALLEL_OK | CURSOR_OPT_NO_SCROLL,
+ false);
+ }
+ }
+
/*
* Construct a JunkFilter we can use to coerce the returned rowtype to the
* desired form, unless the result type is VOID, in which case there's
@@ -795,10 +917,18 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
lazyEvalOK = true;
}
- /* Finally, plan the queries */
- fcache->func_state = init_execution_state(queryTree_list,
- fcache,
- lazyEvalOK);
+ fcache->queryTree_list = queryTree_list;
+ fcache->plansource_list = plansource_list;
+
+ /*
+ * Finally, plan the queries. Skip planning if we use cached plans
+ * machinery - anyway we'll have to replan on the first run when
+ * parameters are substituted.
+ */
+ if (plansource_list == NIL)
+ fcache->func_state = init_execution_state(queryTree_list,
+ fcache,
+ lazyEvalOK);
/* Mark fcache with time of creation to show it's valid */
fcache->lxid = MyProc->vxid.lxid;
@@ -969,7 +1099,13 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
prm->value = MakeExpandedObjectReadOnly(fcinfo->args[i].value,
prm->isnull,
get_typlen(argtypes[i]));
- prm->pflags = 0;
+
+ /*
+ * PARAM_FLAG_CONST is necessary to build efficient custom plan.
+ */
+ if (fcache->plansource_list)
+ prm->pflags = PARAM_FLAG_CONST;
+
prm->ptype = argtypes[i];
}
}
@@ -1022,6 +1158,33 @@ postquel_get_single_result(TupleTableSlot *slot,
return value;
}
+/*
+ * Release plans. This function is called prior to planning
+ * statements with new parameters. When custom plans are generated
+ * for each function call in a statement, they can consume too much memory, so
+ * release them. Generic plans will survive it as plansource holds
+ * reference to a generic plan.
+ */
+static void
+release_plans(List *cplans)
+{
+ ListCell *lc;
+
+ /*
+ * We support separate plan list, so that we visit each plan here only
+ * once
+ */
+ foreach(lc, cplans)
+ {
+ CachedPlan *cplan = lfirst(lc);
+
+ ReleaseCachedPlan(cplan, NULL);
+ }
+
+ /* Cleanup the list itself */
+ list_free(cplans);
+}
+
/*
* fmgr_sql: function call manager for SQL functions
*/
@@ -1040,6 +1203,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
Datum result;
List *eslist;
ListCell *eslc;
+ bool build_cached_plans = false;
/*
* Setup error traceback support for ereport()
@@ -1129,12 +1293,54 @@ fmgr_sql(PG_FUNCTION_ARGS)
break;
}
+ /*
+ * We could skip actual planning if decided to use cached plans. In this
+ * case we have to build cached plans now.
+ */
+ if (fcache->plansource_list != NIL && eslist == NIL)
+ build_cached_plans = true;
+
/*
* Convert params to appropriate format if starting a fresh execution. (If
* continuing execution, we can re-use prior params.)
*/
- if (is_first && es && es->status == F_EXEC_START)
+ if ((is_first && es && es->status == F_EXEC_START) || build_cached_plans)
+ {
postquel_sub_params(fcache, fcinfo);
+ if (fcache->plansource_list)
+ {
+ ListCell *lc;
+
+ /* replan the queries */
+ release_plans(fcache->cplan_list);
+
+ foreach (lc, fcache->func_state)
+ {
+ execution_state *cur_es = lfirst(lc);
+ /*
+ * Execution state can be NULL if statement is
+ * completely replaced by do instead nothing rule.
+ */
+ if (cur_es)
+ pfree(cur_es);
+ }
+ list_free(fcache->func_state);
+
+ fcache->func_state = init_execution_state(fcache->queryTree_list,
+ fcache,
+ lazyEvalOK);
+ /* restore execution state and eslist-related variables */
+ eslist = fcache->func_state;
+ /* find the first non-NULL execution state */
+ foreach(eslc, eslist)
+ {
+ es = (execution_state *) lfirst(eslc);
+
+ if (es)
+ break;
+ }
+ }
+ }
/*
* Build tuplestore to hold results, if we don't have one already. Note
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 55db8f53705..414df0f8f26 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -74,18 +74,6 @@
#include "utils/syscache.h"
-/*
- * We must skip "overhead" operations that involve database access when the
- * cached plan's subject statement is a transaction control command or one
- * that requires a snapshot not to be set yet (such as SET or LOCK). More
- * generally, statements that do not require parse analysis/rewrite/plan
- * activity never need to be revalidated, so we can treat them all like that.
- * For the convenience of postgres.c, treat empty statements that way too.
- */
-#define StmtPlanRequiresRevalidation(plansource) \
- ((plansource)->raw_parse_tree != NULL && \
- stmt_requires_parse_analysis((plansource)->raw_parse_tree))
-
/*
* This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
* those that are in long-lived storage and are examined for sinval events).
@@ -130,6 +118,38 @@ static const ResourceOwnerDesc planref_resowner_desc =
.DebugPrint = NULL /* the default message is fine */
};
+/*
+ * We must skip "overhead" operations that involve database access when the
+ * cached plan's subject statement is a transaction control command or one
+ * that requires a snapshot not to be set yet (such as SET or LOCK). More
+ * generally, statements that do not require parse analysis/rewrite/plan
+ * activity never need to be revalidated, so we can treat them all like that.
+ * For the convenience of postgres.c, treat empty statements that way too.
+ * If plansource doesn't have raw_parse_tree, look at query_list to find out
+ * if there are any non-utility statements.
+ */
+static inline bool
+StmtPlanRequiresRevalidation(CachedPlanSource *plansource)
+{
+ if (plansource->raw_parse_tree != NULL)
+ {
+ return stmt_requires_parse_analysis(plansource->raw_parse_tree);
+ }
+ else
+ {
+ ListCell *lc;
+
+ foreach (lc, plansource->query_list)
+ {
+ Query *query = castNode(Query, lfirst(lc));
+
+ if (query->commandType != CMD_UTILITY)
+ return true;
+ }
+ }
+ return false;
+}
+
/* Convenience wrappers over ResourceOwnerRemember/Forget */
static inline void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 38cb9e970d5..8637bf6b62d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -46,6 +46,7 @@
#include "commands/vacuum.h"
#include "common/file_utils.h"
#include "common/scram-common.h"
+#include "executor/functions.h"
#include "jit/jit.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
@@ -977,6 +978,17 @@ struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+ {
+ {"enable_sql_func_custom_plans", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enable plan cache machinery and custom plans for "
+ "SQL language functions."),
+ NULL,
+ GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
+ },
+ &enable_sql_func_custom_plans,
+ true,
+ NULL, NULL, NULL
+ },
{
{"enable_async_append", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of async append plans."),
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index a6ae2e72d79..34ef847b554 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -53,4 +53,6 @@ extern bool check_sql_fn_retval(List *queryTreeLists,
extern DestReceiver *CreateSQLFunctionDestReceiver(void);
+extern PGDLLIMPORT bool enable_sql_func_custom_plans;
+
#endif /* FUNCTIONS_H */
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 0898107ec30..f5af4dcc173 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -3346,10 +3346,10 @@ select ln(9.342536355e34);
-- invalid inputs
select log(-12.34);
ERROR: cannot take logarithm of a negative number
-CONTEXT: SQL function "log" statement 1
+CONTEXT: SQL function "log" during startup
select log(0.0);
ERROR: cannot take logarithm of zero
-CONTEXT: SQL function "log" statement 1
+CONTEXT: SQL function "log" during startup
-- some random tests
select log(1.234567e-89);
log
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581bd..61a3580411b 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4474,7 +4474,7 @@ create table public.stuffs (stuff text);
SAVEPOINT a;
select error2('nonexistent.stuffs');
ERROR: schema "nonexistent" does not exist
-CONTEXT: SQL function "error1" statement 1
+CONTEXT: SQL function "error1" during startup
PL/pgSQL function error2(text) line 3 at RETURN
ROLLBACK TO a;
select error2('public.stuffs');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 856a8349c50..3e28e4f521b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3868,3 +3868,38 @@ DROP TABLE ruletest_t3;
DROP TABLE ruletest_t2;
DROP TABLE ruletest_t1;
DROP USER regress_rule_user1;
+-- Test that SQL functions correctly handle DO NOTHING rule
+CREATE TABLE some_data (i int, data text);
+CREATE TABLE some_data_values (i int, data text);
+CREATE FUNCTION insert_data(i int, data text)
+RETURNS INT
+AS $$
+INSERT INTO some_data VALUES ($1, $2);
+SELECT 1;
+$$ LANGUAGE SQL;
+INSERT INTO some_data_values SELECT i , 'data'|| i FROM generate_series(1, 10) i;
+CREATE RULE some_data_noinsert AS ON INSERT TO some_data DO INSTEAD NOTHING;
+SELECT insert_data(i, data) FROM some_data_values;
+ insert_data
+-------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+SELECT * FROM some_data ORDER BY i;
+ i | data
+---+------
+(0 rows)
+
+DROP RULE some_data_noinsert ON some_data;
+DROP TABLE some_data_values;
+DROP TABLE some_data;
+DROP FUNCTION insert_data(int, text);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index a8090364532..f7d11afc3dd 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -1378,7 +1378,7 @@ select set_and_report_role();
select set_role_and_error(0);
ERROR: division by zero
-CONTEXT: SQL function "set_role_and_error" statement 1
+CONTEXT: SQL function "set_role_and_error" during startup
set debug_parallel_query = 1;
select set_and_report_role();
set_and_report_role
@@ -1388,7 +1388,7 @@ select set_and_report_role();
select set_role_and_error(0);
ERROR: division by zero
-CONTEXT: SQL function "set_role_and_error" statement 1
+CONTEXT: SQL function "set_role_and_error" during startup
parallel worker
reset debug_parallel_query;
drop function set_and_report_role();
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index b65bb2d5368..7ab5aafcd29 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -511,7 +511,7 @@ SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%|ab#"g' ESCAPE '#') AS "bcdef";
-- Can't have more than two part separators
SELECT SUBSTRING('abcdefg' SIMILAR 'a*#"%#"g*#"x' ESCAPE '#') AS "error";
ERROR: SQL regular expression may not contain more than two escape-double-quote separators
-CONTEXT: SQL function "substring" statement 1
+CONTEXT: SQL function "substring" during startup
-- Postgres extension: with 0 or 1 separator, assume parts 1 and 3 are empty
SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%g' ESCAPE '#') AS "bcdefg";
bcdefg
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 352abc0bd42..7b0a4a0d6c0 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -170,8 +170,9 @@ select name, setting from pg_settings where name like 'enable%';
enable_presorted_aggregate | on
enable_seqscan | on
enable_sort | on
+ enable_sql_func_custom_plans | on
enable_tidscan | on
-(23 rows)
+(24 rows)
-- There are always wait event descriptions for various types. InjectionPoint
-- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index fdd3ff1d161..505449452ee 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1432,3 +1432,27 @@ DROP TABLE ruletest_t2;
DROP TABLE ruletest_t1;
DROP USER regress_rule_user1;
+
+-- Test that SQL functions correctly handle DO NOTHING rule
+CREATE TABLE some_data (i int, data text);
+CREATE TABLE some_data_values (i int, data text);
+
+CREATE FUNCTION insert_data(i int, data text)
+RETURNS INT
+AS $$
+INSERT INTO some_data VALUES ($1, $2);
+SELECT 1;
+$$ LANGUAGE SQL;
+
+INSERT INTO some_data_values SELECT i , 'data'|| i FROM generate_series(1, 10) i;
+
+CREATE RULE some_data_noinsert AS ON INSERT TO some_data DO INSTEAD NOTHING;
+
+SELECT insert_data(i, data) FROM some_data_values;
+
+SELECT * FROM some_data ORDER BY i;
+
+DROP RULE some_data_noinsert ON some_data;
+DROP TABLE some_data_values;
+DROP TABLE some_data;
+DROP FUNCTION insert_data(int, text);
--
2.43.0