Fix planner error with pulling up subquery expressions into function RTEs.
authorTom Lane <[email protected]>
Thu, 14 Oct 2021 16:43:43 +0000 (12:43 -0400)
committerTom Lane <[email protected]>
Thu, 14 Oct 2021 16:43:55 +0000 (12:43 -0400)
If a function-in-FROM laterally references the output of some sub-SELECT
earlier in the FROM clause, and we are able to flatten that sub-SELECT
into the outer query, the expression(s) copied into the function RTE
missed being processed by eval_const_expressions.  This'd lead to trouble
and probable crashes at execution if such expressions contained
named-argument function call syntax or functions with defaulted arguments.
The bug is masked if the query contains any explicit JOIN syntax, which
may help explain why we'd not noticed.

Per bug #17227 from Bernd Dorn.  This is an oversight in commit 7266d0997,
so back-patch to v13 where that came in.

Discussion: https://postgr.es/m/17227-5a28ed1512189fa4@postgresql.org

src/backend/optimizer/plan/planner.c
src/test/regress/expected/rangefuncs.out
src/test/regress/sql/rangefuncs.sql

index 1e42d75465eceb04ce74bfcc6dd60c0cdccbe1fd..bd01ec0526f3dca4d42d0dab80d7e2902eae2e15 100644 (file)
@@ -1084,8 +1084,10 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 
        /*
         * Simplify constant expressions.  For function RTEs, this was already
-        * done by preprocess_function_rtes ... but we have to do it again if the
-        * RTE is LATERAL and might have contained join alias variables.
+        * done by preprocess_function_rtes.  (But note we must do it again for
+        * EXPRKIND_RTFUNC_LATERAL, because those might by now contain
+        * un-simplified subexpressions inserted by flattening of subqueries or
+        * join alias variables.)
         *
         * Note: an essential effect of this is to convert named-argument function
         * calls to positional notation and insert the current actual values of
@@ -1099,8 +1101,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
         * careful to maintain AND/OR flatness --- that is, do not generate a tree
         * with AND directly under AND, nor OR directly under OR.
         */
-       if (!(kind == EXPRKIND_RTFUNC ||
-                 (kind == EXPRKIND_RTFUNC_LATERAL && !root->hasJoinRTEs)))
+       if (kind != EXPRKIND_RTFUNC)
                expr = eval_const_expressions(root, expr);
 
        /*
index cafca1f9aee9a7d9580723eb9d2d2708d3a84686..2334a1321e374c221c30e21dc18f9ab0f92c8279 100644 (file)
@@ -2416,3 +2416,32 @@ select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u;
 (0 rows)
 
 drop type rngfunc2;
+-- check handling of functions pulled up into function RTEs (bug #17227)
+explain (verbose, costs off)
+select * from
+  (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+   from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+        as unnested_modules(module)) as ss,
+  jsonb_to_recordset(ss.lecture) as j (id text);
+                                                                       QUERY PLAN                                                                       
+--------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false), j.id
+   ->  Function Scan on pg_catalog.unnest unnested_modules
+         Output: unnested_modules.module
+         Function Call: unnest('{"{\"lectures\": [{\"id\": \"1\"}]}"}'::jsonb[])
+   ->  Function Scan on pg_catalog.jsonb_to_recordset j
+         Output: j.id
+         Function Call: jsonb_to_recordset(jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false))
+(8 rows)
+
+select * from
+  (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+   from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+        as unnested_modules(module)) as ss,
+  jsonb_to_recordset(ss.lecture) as j (id text);
+    lecture    | id 
+---------------+----
+ [{"id": "1"}] | 1
+(1 row)
+
index 3c436028daffd3afb9043aa8ab158aabcec3ab1b..7e5cde14c4aff2caec1640d77e2728c739dfc29d 100644 (file)
@@ -767,3 +767,18 @@ select *, row_to_json(u) from unnest(array[null::rngfunc2, (1,'foo')::rngfunc2,
 select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u;
 
 drop type rngfunc2;
+
+-- check handling of functions pulled up into function RTEs (bug #17227)
+
+explain (verbose, costs off)
+select * from
+  (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+   from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+        as unnested_modules(module)) as ss,
+  jsonb_to_recordset(ss.lecture) as j (id text);
+
+select * from
+  (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+   from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+        as unnested_modules(module)) as ss,
+  jsonb_to_recordset(ss.lecture) as j (id text);