diff options
Diffstat (limited to 'src/test/regress')
| -rw-r--r-- | src/test/regress/expected/create_function_sql.out | 10 | ||||
| -rw-r--r-- | src/test/regress/expected/misc_functions.out | 50 | ||||
| -rw-r--r-- | src/test/regress/expected/rangefuncs.out | 4 | ||||
| -rw-r--r-- | src/test/regress/expected/returning.out | 4 | ||||
| -rw-r--r-- | src/test/regress/expected/rowtypes.out | 10 | ||||
| -rw-r--r-- | src/test/regress/regress.c | 121 | ||||
| -rw-r--r-- | src/test/regress/sql/create_function_sql.sql | 11 | ||||
| -rw-r--r-- | src/test/regress/sql/misc_functions.sql | 34 |
8 files changed, 244 insertions, 0 deletions
diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out index 73c6730d459..42524230d2b 100644 --- a/src/test/regress/expected/create_function_sql.out +++ b/src/test/regress/expected/create_function_sql.out @@ -455,6 +455,16 @@ DROP TABLE functest3 CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to view functestv3 drop cascades to function functest_s_14() +-- Check reporting of temporary-object dependencies within SQL-standard body +-- (tests elsewhere already cover dependencies on arg and result types) +CREATE TEMP SEQUENCE mytempseq; +CREATE FUNCTION functest_tempseq() RETURNS int + RETURN nextval('mytempseq'); +NOTICE: function "functest_tempseq" will be effectively temporary +DETAIL: It depends on temporary sequence mytempseq. +-- This discards mytempseq and therefore functest_tempseq(). If it fails to, +-- the function will appear in the information_schema tests below. +DISCARD TEMP; -- information_schema tests CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo') RETURNS int diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index e76e28b95ce..d7d965d884a 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -808,6 +808,56 @@ false, true, false, true); Function Scan on generate_series g (cost=N..N rows=1000 width=N) (1 row) +-- +-- Test SupportRequestInlineInFrom request +-- +CREATE FUNCTION test_inline_in_from_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_in_from_support_func' + LANGUAGE C STRICT; +CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF TEXT +LANGUAGE plpgsql +AS $function$ +DECLARE + sql TEXT; +BEGIN + sql := format('SELECT %I::text FROM %I', colname, tablename); + IF filter IS NOT NULL THEN + sql := CONCAT(sql, format(' WHERE %I::text = $1', colname)); + END IF; + RETURN QUERY EXECUTE sql USING filter; +END; +$function$ STABLE; +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) + SUPPORT test_inline_in_from_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + foo_from_bar +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + foo_from_bar +-------------- + doh! +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +---------------------- + Seq Scan on text_tbl +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +------------------------------- + Seq Scan on text_tbl + Filter: (f1 = 'doh!'::text) +(2 rows) + +DROP FUNCTION foo_from_bar; -- Test functions for control data SELECT count(*) > 0 AS ok FROM pg_control_checkpoint(); ok diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 30241e22da2..5cc94011e97 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2178,6 +2178,8 @@ alter table users drop column todrop; create or replace function get_first_user() returns users as $$ SELECT * FROM users ORDER BY userid LIMIT 1; $$ language sql stable; +NOTICE: function "get_first_user" will be effectively temporary +DETAIL: It depends on temporary type users. SELECT get_first_user(); get_first_user ------------------- @@ -2193,6 +2195,8 @@ SELECT * FROM get_first_user(); create or replace function get_users() returns setof users as $$ SELECT * FROM users ORDER BY userid; $$ language sql stable; +NOTICE: function "get_users" will be effectively temporary +DETAIL: It depends on temporary type users. SELECT get_users(); get_users --------------------- diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index d02c2ceab53..cfaaf015bb3 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -306,6 +306,8 @@ SELECT * FROM foo; -- Check use of a whole-row variable for an inlined set-returning function CREATE FUNCTION foo_f() RETURNS SETOF foo AS $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +NOTICE: function "foo_f" will be effectively temporary +DETAIL: It depends on temporary type foo. UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 RETURNING foo_f; foo_f @@ -930,6 +932,8 @@ BEGIN ATOMIC (SELECT count(*) FROM foo WHERE foo = o), (SELECT count(*) FROM foo WHERE foo = n); END; +NOTICE: function "foo_update" will be effectively temporary +DETAIL: It depends on temporary table foo. \sf foo_update CREATE OR REPLACE FUNCTION public.foo_update() RETURNS void diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 677ad2ab9ad..956bc2d02fc 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -907,6 +907,8 @@ create temp table compos (f1 int, f2 text); create function fcompos1(v compos) returns void as $$ insert into compos values (v); -- fail $$ language sql; +NOTICE: function "fcompos1" will be effectively temporary +DETAIL: It depends on temporary type compos. ERROR: column "f1" is of type integer but expression is of type compos LINE 2: insert into compos values (v); -- fail ^ @@ -914,12 +916,18 @@ HINT: You will need to rewrite or cast the expression. create function fcompos1(v compos) returns void as $$ insert into compos values (v.*); $$ language sql; +NOTICE: function "fcompos1" will be effectively temporary +DETAIL: It depends on temporary type compos. create function fcompos2(v compos) returns void as $$ select fcompos1(v); $$ language sql; +NOTICE: function "fcompos2" will be effectively temporary +DETAIL: It depends on temporary type compos. create function fcompos3(v compos) returns void as $$ select fcompos1(fcompos3.v.*); $$ language sql; +NOTICE: function "fcompos3" will be effectively temporary +DETAIL: It depends on temporary type compos. select fcompos1(row(1,'one')); fcompos1 ---------- @@ -1012,6 +1020,8 @@ select last(f) from fullname f; create function longname(fullname) returns text language sql as $$select $1.first || ' ' || $1.last$$; +NOTICE: function "longname" will be effectively temporary +DETAIL: It depends on temporary type fullname. select f.longname from fullname f; longname ---------- diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index a2db6080876..56cc0567b1c 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -28,6 +28,7 @@ #include "commands/sequence.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "executor/functions.h" #include "executor/spi.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -39,6 +40,7 @@ #include "port/atomics.h" #include "postmaster/postmaster.h" /* for MAX_BACKENDS */ #include "storage/spin.h" +#include "tcop/tcopprot.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/geo_decls.h" @@ -803,6 +805,125 @@ test_support_func(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } +PG_FUNCTION_INFO_V1(test_inline_in_from_support_func); +Datum +test_inline_in_from_support_func(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestInlineInFrom)) + { + /* + * Assume that the target is foo_from_bar; that's safe as long as we + * don't attach this to any other function. + */ + SupportRequestInlineInFrom *req = (SupportRequestInlineInFrom *) rawreq; + StringInfoData sql; + RangeTblFunction *rtfunc = req->rtfunc; + FuncExpr *expr = (FuncExpr *) rtfunc->funcexpr; + Node *node; + Const *c; + char *colname; + char *tablename; + SQLFunctionParseInfoPtr pinfo; + List *raw_parsetree_list; + List *querytree_list; + Query *querytree; + + if (list_length(expr->args) != 3) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with %d args but expected 3", list_length(expr->args)))); + PG_RETURN_POINTER(NULL); + } + + /* Get colname */ + node = linitial(expr->args); + if (!IsA(node, Const)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID || c->constisnull) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters"))); + PG_RETURN_POINTER(NULL); + } + colname = TextDatumGetCString(c->constvalue); + + /* Get tablename */ + node = lsecond(expr->args); + if (!IsA(node, Const)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID || c->constisnull) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters"))); + PG_RETURN_POINTER(NULL); + } + tablename = TextDatumGetCString(c->constvalue); + + /* Begin constructing replacement SELECT query. */ + initStringInfo(&sql); + appendStringInfo(&sql, "SELECT %s::text FROM %s", + quote_identifier(colname), + quote_identifier(tablename)); + + /* Add filter expression if present. */ + node = lthird(expr->args); + if (!(IsA(node, Const) && ((Const *) node)->constisnull)) + { + /* + * We only filter if $3 is not constant-NULL. This is not a very + * exact implementation of the PL/pgSQL original, but it's close + * enough for demonstration purposes. + */ + appendStringInfo(&sql, " WHERE %s::text = $3", + quote_identifier(colname)); + } + + /* Build a SQLFunctionParseInfo with the parameters of my function. */ + pinfo = prepare_sql_fn_parse_info(req->proc, + (Node *) expr, + expr->inputcollid); + + /* Parse the generated SQL. */ + raw_parsetree_list = pg_parse_query(sql.data); + if (list_length(raw_parsetree_list) != 1) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func parsed to more than one node"))); + PG_RETURN_POINTER(NULL); + } + + /* Analyze the parse tree as if it were a SQL-language body. */ + querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list), + sql.data, + (ParserSetupHook) sql_fn_parser_setup, + pinfo, NULL); + if (list_length(querytree_list) != 1) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func rewrote to more than one node"))); + PG_RETURN_POINTER(NULL); + } + + querytree = linitial(querytree_list); + if (!IsA(querytree, Query)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func didn't parse to a Query"))); + PG_RETURN_POINTER(NULL); + } + + PG_RETURN_POINTER(querytree); + } + + PG_RETURN_POINTER(NULL); +} + PG_FUNCTION_INFO_V1(test_opclass_options_func); Datum test_opclass_options_func(PG_FUNCTION_ARGS) diff --git a/src/test/regress/sql/create_function_sql.sql b/src/test/regress/sql/create_function_sql.sql index 3d5f2a92093..4543273f93a 100644 --- a/src/test/regress/sql/create_function_sql.sql +++ b/src/test/regress/sql/create_function_sql.sql @@ -241,6 +241,17 @@ SELECT functest_S_14(); DROP TABLE functest3 CASCADE; +-- Check reporting of temporary-object dependencies within SQL-standard body +-- (tests elsewhere already cover dependencies on arg and result types) +CREATE TEMP SEQUENCE mytempseq; + +CREATE FUNCTION functest_tempseq() RETURNS int + RETURN nextval('mytempseq'); + +-- This discards mytempseq and therefore functest_tempseq(). If it fails to, +-- the function will appear in the information_schema tests below. +DISCARD TEMP; + -- information_schema tests diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql index 220472d5ad1..0fc20fbb6b4 100644 --- a/src/test/regress/sql/misc_functions.sql +++ b/src/test/regress/sql/misc_functions.sql @@ -360,6 +360,40 @@ SELECT explain_mask_costs($$ SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$, false, true, false, true); +-- +-- Test SupportRequestInlineInFrom request +-- + +CREATE FUNCTION test_inline_in_from_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_in_from_support_func' + LANGUAGE C STRICT; + +CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF TEXT +LANGUAGE plpgsql +AS $function$ +DECLARE + sql TEXT; +BEGIN + sql := format('SELECT %I::text FROM %I', colname, tablename); + IF filter IS NOT NULL THEN + sql := CONCAT(sql, format(' WHERE %I::text = $1', colname)); + END IF; + RETURN QUERY EXECUTE sql USING filter; +END; +$function$ STABLE; + +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) + SUPPORT test_inline_in_from_support_func; + +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + +DROP FUNCTION foo_from_bar; + -- Test functions for control data SELECT count(*) > 0 AS ok FROM pg_control_checkpoint(); SELECT count(*) > 0 AS ok FROM pg_control_init(); |
