0004-Handle-SQL-functions-which-result-type-is-adjuisted.patch
text/x-diff
Filename: 0004-Handle-SQL-functions-which-result-type-is-adjuisted.patch
Type: text/x-diff
Part: 0
From 19c91b224dbf4342f8305037c95cc90d357c08d9 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 5 Mar 2025 18:51:37 +0300
Subject: [PATCH 4/4] Handle SQL functions which result type is adjuisted
Query could be modified between rewrite and plan stages by
check_sql_fn_retval(). We move this step earlier, so that
cached plans were created with already modified tlist. In this
case if later revalidation is considered by RevalidateCachedQuery(),
modifications, done by check_sql_fn_retval(), will not be lost.
We consider that rewriting query cannot ever changes the targetlist
results.
Note that test_extensions result has changed as cached query
can be revalidated after extension is moved to another schema -
function oid in the query still matches the existing one.
---
src/backend/executor/functions.c | 76 ++++++++++---------
.../expected/test_extensions.out | 11 ++-
2 files changed, 44 insertions(+), 43 deletions(-)
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 06efcec4e7c..48db2ef061a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -25,6 +25,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
@@ -948,6 +949,7 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
SQLFunctionPlanKey plan_cache_entry_key;
bool use_plan_cache;
bool plan_cache_entry_valid;
+ List *query_list;
/*
* Create memory context that holds all the SQLFunctionCache data. It
@@ -1098,32 +1100,17 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
plansource_list = NIL;
- queryTree_list = NIL;
+ /* Construct a list of analyzed parsetrees. */
+ query_list = NIL;
if (!isNull)
{
Node *n;
- List *stored_query_list;
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
- stored_query_list = linitial_node(List, castNode(List, n));
+ query_list = linitial_node(List, castNode(List, n));
else
- stored_query_list = list_make1(n);
-
- foreach(lc, stored_query_list)
- {
- Query *parsetree = lfirst_node(Query, lc);
- List *queryTree_sublist;
- CachedPlanSource *plansource;
-
- AcquireRewriteLocks(parsetree, true, false);
-
- plansource = CreateCachedPlanForQuery(parsetree, fcache->src, CreateCommandTag((Node *) parsetree));
- plansource_list = lappend(plansource_list, plansource);
-
- queryTree_sublist = pg_rewrite_query(parsetree);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
- }
+ query_list = list_make1(n);
}
else
{
@@ -1134,25 +1121,15 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
foreach(lc, raw_parsetree_list)
{
RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
- CachedPlanSource *plansource;
-
- plansource = CreateCachedPlan(parsetree, fcache->src, CreateCommandTag(parsetree->stmt));
- plansource_list = lappend(plansource_list, plansource);
-
- queryTree_sublist = pg_analyze_and_rewrite_withcb(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Query *query;
+
+ query = parse_analyze_withcb(parsetree, fcache->src, (ParserSetupHook) sql_fn_parser_setup, fcache->pinfo,
+ NULL);
+
+ query_list = lappend(query_list, query);
}
}
- /*
- * Check that there are no statements we don't want to allow.
- */
- check_sql_fn_statements(queryTree_list);
/*
* Check that the function returns the type it claims to. Although in
@@ -1162,7 +1139,10 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
* the function had any polymorphic arguments. Moreover,
* check_sql_fn_retval takes care of injecting any required column
* type coercions. (But we don't ask it to insert nulls for dropped
- * columns; the junkfilter handles that.)
+ * columns; the junkfilter handles that.) As check_sql_fn_retval() can
+ * modify queries to match expected return types, we execute it prior
+ * to creating cached plans, so that if revalidation happens and
+ * triggers query rewriting, return type would be already correct.
*
* Note: we set fcache->returnsTuple according to whether we are
* returning the whole tuple result or just a single column. In the
@@ -1174,13 +1154,35 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
* do that.)
*/
- fcache->returnsTuple = check_sql_fn_retval(queryTree_list,
+ fcache->returnsTuple = check_sql_fn_retval(list_make1(query_list),
rettype,
rettupdesc,
procedureStruct->prokind,
false,
&resulttlist);
+ queryTree_list = NIL;
+
+ foreach(lc, query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+ CachedPlanSource *plansource;
+
+ AcquireRewriteLocks(parsetree, true, false);
+
+ plansource = CreateCachedPlanForQuery(parsetree, fcache->src, CreateCommandTag((Node *) parsetree));
+ plansource_list = lappend(plansource_list, plansource);
+
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+
+ /*
+ * Check that there are no statements we don't want to allow.
+ */
+ check_sql_fn_statements(queryTree_list);
+
/*
* Queries could be rewritten by check_sql_fn_retval(). Now when they
* have their final form, we can complete plan cache entry creation.
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 72bae1bf254..ea3d4ca61d8 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -646,12 +646,11 @@ SELECT dep_req3();
(1 row)
SELECT dep_req3b(); -- fails
-ERROR: function public.dep_req2() does not exist
-LINE 1: SELECT public.dep_req2() || ' req3b'
- ^
-HINT: No function matches the given name and argument types. You might need to add explicit type casts.
-QUERY: SELECT public.dep_req2() || ' req3b'
-CONTEXT: SQL function "dep_req3b" statement 1
+ dep_req3b
+-----------------
+ req1 req2 req3b
+(1 row)
+
DROP EXTENSION test_ext_req_schema3;
ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2; -- now ok
SELECT test_s_dep2.dep_req1();
--
2.43.0