Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 877cdf1

Browse files
committed
Mop-up for letting VOID-returning SQL functions end with a SELECT.
Part of the intent in commit fd1a421 was to allow SQL functions that are declared to return VOID to contain anything, including an unrelated final SELECT, the same as SQL-language procedures can. However, the planner's inlining logic didn't get that memo. Fix it, and add some regression tests covering this area, since evidently we had none. In passing, clean up some typos in comments in create_function_3.sql, and get rid of its none-too-safe assumption that DROP CASCADE notice output is immutably ordered. Per report from Prabhat Sahu. Discussion: https://postgr.es/m/CANEvxPqxAj6nNHVcaXxpTeEFPmh24Whu+23emgjiuKrhJSct0A@mail.gmail.com
1 parent 84a3611 commit 877cdf1

File tree

3 files changed

+130
-33
lines changed

3 files changed

+130
-33
lines changed

src/backend/optimizer/util/clauses.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4487,8 +4487,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
44874487
* properties. (The prokind and nargs checks are just paranoia.)
44884488
*/
44894489
if (funcform->prolang != SQLlanguageId ||
4490-
funcform->prosecdef ||
44914490
funcform->prokind != PROKIND_FUNCTION ||
4491+
funcform->prosecdef ||
44924492
funcform->proretset ||
44934493
funcform->prorettype == RECORDOID ||
44944494
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig) ||
@@ -4623,9 +4623,18 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
46234623
/* Now we can grab the tlist expression */
46244624
newexpr = (Node *) ((TargetEntry *) linitial(querytree->targetList))->expr;
46254625

4626-
/* Assert that check_sql_fn_retval did the right thing */
4627-
Assert(exprType(newexpr) == result_type);
4628-
/* It couldn't have made any dangerous tlist changes, either */
4626+
/*
4627+
* If the SQL function returns VOID, we can only inline it if it is a
4628+
* SELECT of an expression returning VOID (ie, it's just a redirection to
4629+
* another VOID-returning function). In all non-VOID-returning cases,
4630+
* check_sql_fn_retval should ensure that newexpr returns the function's
4631+
* declared result type, so this test shouldn't fail otherwise; but we may
4632+
* as well cope gracefully if it does.
4633+
*/
4634+
if (exprType(newexpr) != result_type)
4635+
goto fail;
4636+
4637+
/* check_sql_fn_retval couldn't have made any dangerous tlist changes */
46294638
Assert(!modifyTargetList);
46304639

46314640
/*
@@ -5010,12 +5019,16 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
50105019
* properties. In particular it mustn't be declared STRICT, since we
50115020
* couldn't enforce that. It also mustn't be VOLATILE, because that is
50125021
* supposed to cause it to be executed with its own snapshot, rather than
5013-
* sharing the snapshot of the calling query. (Rechecking proretset is
5014-
* just paranoia.)
5022+
* sharing the snapshot of the calling query. We also disallow returning
5023+
* SETOF VOID, because inlining would result in exposing the actual result
5024+
* of the function's last SELECT, which should not happen in that case.
5025+
* (Rechecking prokind and proretset is just paranoia.)
50155026
*/
50165027
if (funcform->prolang != SQLlanguageId ||
5028+
funcform->prokind != PROKIND_FUNCTION ||
50175029
funcform->proisstrict ||
50185030
funcform->provolatile == PROVOLATILE_VOLATILE ||
5031+
funcform->prorettype == VOIDOID ||
50195032
funcform->prosecdef ||
50205033
!funcform->proretset ||
50215034
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig))

src/test/regress/expected/create_function_3.out

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
--
22
-- CREATE FUNCTION
33
--
4-
-- sanity check of pg_proc catalog to the given parameters
4+
-- Assorted tests using SQL-language functions
55
--
6+
-- All objects made in this test are in temp_func_test schema
67
CREATE USER regress_unpriv_user;
78
CREATE SCHEMA temp_func_test;
89
GRANT ALL ON SCHEMA temp_func_test TO public;
910
SET search_path TO temp_func_test, public;
1011
--
12+
-- Make sanity checks on the pg_proc entries created by CREATE FUNCTION
13+
--
14+
--
1115
-- ARGUMENT and RETURN TYPES
1216
--
1317
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
@@ -127,7 +131,7 @@ SELECT proname, proleakproof FROM pg_proc
127131
functest_e_2 | t
128132
(2 rows)
129133

130-
ALTER FUNCTION functest_E_2(int) NOT LEAKPROOF; -- remove leakproog attribute
134+
ALTER FUNCTION functest_E_2(int) NOT LEAKPROOF; -- remove leakproof attribute
131135
SELECT proname, proleakproof FROM pg_proc
132136
WHERE oid in ('functest_E_1'::regproc,
133137
'functest_E_2'::regproc) ORDER BY proname;
@@ -137,7 +141,7 @@ SELECT proname, proleakproof FROM pg_proc
137141
functest_e_2 | f
138142
(2 rows)
139143

140-
-- it takes superuser privilege to turn on leakproof, but not for turn off
144+
-- it takes superuser privilege to turn on leakproof, but not to turn off
141145
ALTER FUNCTION functest_E_1(int) OWNER TO regress_unpriv_user;
142146
ALTER FUNCTION functest_E_2(int) OWNER TO regress_unpriv_user;
143147
SET SESSION AUTHORIZATION regress_unpriv_user;
@@ -146,7 +150,7 @@ ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
146150
ALTER FUNCTION functest_E_2(int) LEAKPROOF;
147151
ERROR: only superuser can define a leakproof function
148152
CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
149-
LEAKPROOF AS 'SELECT $1 < 200'; -- failed
153+
LEAKPROOF AS 'SELECT $1 < 200'; -- fail
150154
ERROR: only superuser can define a leakproof function
151155
RESET SESSION AUTHORIZATION;
152156
--
@@ -280,24 +284,66 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
280284
ERROR: cannot change routine kind
281285
DETAIL: "functest1" is a function.
282286
DROP FUNCTION functest1(a int);
283-
-- Cleanups
287+
-- Check behavior of VOID-returning SQL functions
288+
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
289+
$$ SELECT a + 1 $$;
290+
SELECT voidtest1(42);
291+
voidtest1
292+
-----------
293+
294+
(1 row)
295+
296+
CREATE FUNCTION voidtest2(a int, b int) RETURNS VOID LANGUAGE SQL AS
297+
$$ SELECT voidtest1(a + b) $$;
298+
SELECT voidtest2(11,22);
299+
voidtest2
300+
-----------
301+
302+
(1 row)
303+
304+
-- currently, we can inline voidtest2 but not voidtest1
305+
EXPLAIN (verbose, costs off) SELECT voidtest2(11,22);
306+
QUERY PLAN
307+
-------------------------
308+
Result
309+
Output: voidtest1(33)
310+
(2 rows)
311+
312+
CREATE TEMP TABLE sometable(f1 int);
313+
CREATE FUNCTION voidtest3(a int) RETURNS VOID LANGUAGE SQL AS
314+
$$ INSERT INTO sometable VALUES(a + 1) $$;
315+
SELECT voidtest3(17);
316+
voidtest3
317+
-----------
318+
319+
(1 row)
320+
321+
CREATE FUNCTION voidtest4(a int) RETURNS VOID LANGUAGE SQL AS
322+
$$ INSERT INTO sometable VALUES(a - 1) RETURNING f1 $$;
323+
SELECT voidtest4(39);
324+
voidtest4
325+
-----------
326+
327+
(1 row)
328+
329+
TABLE sometable;
330+
f1
331+
----
332+
18
333+
38
334+
(2 rows)
335+
336+
CREATE FUNCTION voidtest5(a int) RETURNS SETOF VOID LANGUAGE SQL AS
337+
$$ SELECT generate_series(1, a) $$ STABLE;
338+
SELECT * FROM voidtest5(3);
339+
voidtest5
340+
-----------
341+
(0 rows)
342+
343+
-- Cleanup
344+
\set VERBOSITY terse \\ -- suppress cascade details
284345
DROP SCHEMA temp_func_test CASCADE;
285-
NOTICE: drop cascades to 16 other objects
286-
DETAIL: drop cascades to function functest_a_1(text,date)
287-
drop cascades to function functest_a_2(text[])
288-
drop cascades to function functest_a_3()
289-
drop cascades to function functest_b_2(integer)
290-
drop cascades to function functest_b_3(integer)
291-
drop cascades to function functest_b_4(integer)
292-
drop cascades to function functest_c_1(integer)
293-
drop cascades to function functest_c_2(integer)
294-
drop cascades to function functest_c_3(integer)
295-
drop cascades to function functest_e_1(integer)
296-
drop cascades to function functest_e_2(integer)
297-
drop cascades to function functest_f_1(integer)
298-
drop cascades to function functest_f_2(integer)
299-
drop cascades to function functest_f_3(integer)
300-
drop cascades to function functest_f_4(integer)
301-
drop cascades to function functest_b_2(bigint)
346+
NOTICE: drop cascades to 21 other objects
347+
\set VERBOSITY default
302348
DROP USER regress_unpriv_user;
303349
RESET search_path;

src/test/regress/sql/create_function_3.sql

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
--
22
-- CREATE FUNCTION
33
--
4-
-- sanity check of pg_proc catalog to the given parameters
4+
-- Assorted tests using SQL-language functions
55
--
6+
7+
-- All objects made in this test are in temp_func_test schema
8+
69
CREATE USER regress_unpriv_user;
710

811
CREATE SCHEMA temp_func_test;
912
GRANT ALL ON SCHEMA temp_func_test TO public;
1013

1114
SET search_path TO temp_func_test, public;
1215

16+
--
17+
-- Make sanity checks on the pg_proc entries created by CREATE FUNCTION
18+
--
19+
1320
--
1421
-- ARGUMENT and RETURN TYPES
1522
--
@@ -88,12 +95,12 @@ SELECT proname, proleakproof FROM pg_proc
8895
WHERE oid in ('functest_E_1'::regproc,
8996
'functest_E_2'::regproc) ORDER BY proname;
9097

91-
ALTER FUNCTION functest_E_2(int) NOT LEAKPROOF; -- remove leakproog attribute
98+
ALTER FUNCTION functest_E_2(int) NOT LEAKPROOF; -- remove leakproof attribute
9299
SELECT proname, proleakproof FROM pg_proc
93100
WHERE oid in ('functest_E_1'::regproc,
94101
'functest_E_2'::regproc) ORDER BY proname;
95102

96-
-- it takes superuser privilege to turn on leakproof, but not for turn off
103+
-- it takes superuser privilege to turn on leakproof, but not to turn off
97104
ALTER FUNCTION functest_E_1(int) OWNER TO regress_unpriv_user;
98105
ALTER FUNCTION functest_E_2(int) OWNER TO regress_unpriv_user;
99106

@@ -103,7 +110,7 @@ ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
103110
ALTER FUNCTION functest_E_2(int) LEAKPROOF;
104111

105112
CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
106-
LEAKPROOF AS 'SELECT $1 < 200'; -- failed
113+
LEAKPROOF AS 'SELECT $1 < 200'; -- fail
107114

108115
RESET SESSION AUTHORIZATION;
109116

@@ -183,7 +190,38 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
183190
DROP FUNCTION functest1(a int);
184191

185192

186-
-- Cleanups
193+
-- Check behavior of VOID-returning SQL functions
194+
195+
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
196+
$$ SELECT a + 1 $$;
197+
SELECT voidtest1(42);
198+
199+
CREATE FUNCTION voidtest2(a int, b int) RETURNS VOID LANGUAGE SQL AS
200+
$$ SELECT voidtest1(a + b) $$;
201+
SELECT voidtest2(11,22);
202+
203+
-- currently, we can inline voidtest2 but not voidtest1
204+
EXPLAIN (verbose, costs off) SELECT voidtest2(11,22);
205+
206+
CREATE TEMP TABLE sometable(f1 int);
207+
208+
CREATE FUNCTION voidtest3(a int) RETURNS VOID LANGUAGE SQL AS
209+
$$ INSERT INTO sometable VALUES(a + 1) $$;
210+
SELECT voidtest3(17);
211+
212+
CREATE FUNCTION voidtest4(a int) RETURNS VOID LANGUAGE SQL AS
213+
$$ INSERT INTO sometable VALUES(a - 1) RETURNING f1 $$;
214+
SELECT voidtest4(39);
215+
216+
TABLE sometable;
217+
218+
CREATE FUNCTION voidtest5(a int) RETURNS SETOF VOID LANGUAGE SQL AS
219+
$$ SELECT generate_series(1, a) $$ STABLE;
220+
SELECT * FROM voidtest5(3);
221+
222+
-- Cleanup
223+
\set VERBOSITY terse \\ -- suppress cascade details
187224
DROP SCHEMA temp_func_test CASCADE;
225+
\set VERBOSITY default
188226
DROP USER regress_unpriv_user;
189227
RESET search_path;

0 commit comments

Comments
 (0)