blob: 5603adf4b1ab26b4b6ee68bf2338722a3f5f612f [file] [log] [blame]
-- =================== -*- mode: sql; -*- ===================
-- table_functions.sql
--
-- Test for enhancements to table function support
-- ===================
CREATE SCHEMA table_function;
SET search_path TO table_function, public;
CREATE TABLE example(a int, b text) DISTRIBUTED by (a);
COPY example FROM stdin;
1 value1.1/4
2 value2.1/3
3 value3.1/2
4 value4.1/1
1 value1.2/4
2 value2.2/3
3 value3.2/2
1 value1.3/4
2 value2.3/3
1 value1.4/4
\.
CREATE TABLE history(id integer, time timestamp) DISTRIBUTED BY (id);
COPY history FROM stdin;
1 2011/08/21 10:15:02am
1 2011/08/21 10:15:30am
1 2011/08/22 10:15:04am
1 2011/08/22 10:16:10am
2 2011/08/21 10:15:02am
2 2011/08/21 10:15:02pm
2 2011/08/21 10:16:02am
2 2011/08/21 10:16:02pm
3 2011-08-19 19:05:13
3 2011-08-19 19:06:50
3 2011-08-19 19:07:35
3 2011-08-19 19:08:18
3 2011-08-19 19:09:07
3 2011-08-20 10:07:10
3 2011-08-20 10:07:35
3 2011-08-20 10:11:29
3 2011-08-20 10:17:10
3 2011-08-20 10:17:42
\.
CREATE TABLE example_r AS SELECT * FROM example DISTRIBUTED RANDOMLY;
CREATE VIEW example_v AS SELECT * FROM example;
/* Attempt to get relatively stable plans */
set gp_segments_for_planner=8;
analyze example;
analyze example_r;
/* Create some normal scalar input / scalar output functions */
CREATE FUNCTION scalar_1(int) RETURNS int
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
CREATE FUNCTION scalar_2(IN int) RETURNS int
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
CREATE FUNCTION scalar_3(IN a int, OUT b INT)
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
CREATE FUNCTION scalar_4(INOUT x int)
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
CREATE FUNCTION scalar_5(anyelement) RETURNS anyelement
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
/* Create some normal scalar input / table output functions */
CREATE FUNCTION scalar_tf_1(int) RETURNS TABLE(a int, b text)
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION scalar_tf_2(IN int) RETURNS TABLE(a int, b text)
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION scalar_tf_3(IN a int) RETURNS TABLE(a int, b text)
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION scalar_tf_4(IN a int, OUT a int, OUT b text) RETURNS SETOF RECORD
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION scalar_tf_5(IN a int) RETURNS SETOF RECORD
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION scalar_tf_6(IN a anyelement) RETURNS SETOF example
AS $$ SELECT a+$1, b from example $$ LANGUAGE SQL READS SQL DATA;
/* CREATE some multiset input table functions */
\getenv abs_builddir PG_ABS_BUILDDIR
\set regress_dll :abs_builddir '/regress.so'
/* scalar value outputs */
CREATE FUNCTION multiset_scalar_null(anytable) RETURNS int
AS :'regress_dll', 'multiset_scalar_null' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_scalar_value(anytable) RETURNS int
AS :'regress_dll', 'multiset_scalar_value' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_scalar_tuple(anytable) RETURNS example
AS :'regress_dll', 'multiset_scalar_tuple' LANGUAGE C READS SQL DATA;
/* set value outputs */
CREATE FUNCTION multiset_setof_null(anytable) RETURNS setof int
AS :'regress_dll', 'multiset_setof_null' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_setof_value(anytable) RETURNS setof int
AS :'regress_dll', 'multiset_setof_value' LANGUAGE C READS SQL DATA;
/* Bunches of different ways of saying "returns a setof rows */
CREATE FUNCTION multiset_materialize_good(anytable)
RETURNS TABLE(a int, b text)
AS :'regress_dll', 'multiset_materialize_good' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_materialize_bad(anytable)
RETURNS TABLE(a int, b text)
AS :'regress_dll', 'multiset_materialize_bad' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_1(a anytable) RETURNS TABLE(a int, b text)
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_2(a anytable) RETURNS TABLE(a int, b text)
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_3(a anytable, out a int, out b text) RETURNS SETOF RECORD
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_4(a anytable) RETURNS SETOF RECORD
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_5(a anytable) RETURNS SETOF example
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
CREATE FUNCTION multiset_6(a anytable) RETURNS SETOF record
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
/* Negative test cases around CREATE FUNCTION */
/* ERROR: TABLE output not allowed with OUT parameters */
CREATE FUNCTION error(OUT a int) RETURNS TABLE(a int, b text)
AS $$ SELECT * from example $$ LANGUAGE SQL READS SQL DATA;
/* ERROR: Multiple multiset inputs not supported */
CREATE FUNCTION error(a anytable, b anytable) RETURNS TABLE(a int, b text)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
/* ERROR: Multiset outputs not supported */
CREATE FUNCTION error(OUT anytable)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error() RETURNS TABLE(x anytable)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
/* ERROR: SETOF as IN/OUT/TABLE parameters is not supported */
CREATE FUNCTION error(OUT setof example)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error(IN setof example)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error(INOUT setof example)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
CREATE FUNCTION error() RETURNS TABLE(a setof example)
AS $$ SELECT * FROM example $$ LANGUAGE SQL READS SQL DATA;
/* ERROR: anytable cannot have default value */
CREATE FUNCTION error(a anytable DEFAULT TABLE(select 1,'test')) RETURNS TABLE(a int, b text)
AS :'regress_dll', 'multiset_example' LANGUAGE C;
/* Negative test cases around the "anytable" type */
CREATE TABLE fail(x anytable);
CREATE TYPE fail AS (x anytable);
/* Observe how they are represented in the catalog */
select
p.proname,
p.oid::regproc,
p.oid::regprocedure,
p.proretset,
(p.proargtypes::regtype[])[0:0],
p.prorettype::regtype,
p.proargmodes
FROM pg_proc p join pg_namespace n ON (p.pronamespace = n.oid)
WHERE n.nspname = 'table_function'
ORDER BY p.proname;
/* Observe how psql reports them */
\df (scalar_*|multiset_*)
-- Normal function use:
-- Normal scalar input / scalar output functions
-- begin equivalent
SELECT scalar_1(5) as b;
SELECT scalar_2(5) as b;
SELECT scalar_3(5) as b;
SELECT scalar_4(5) as b;
SELECT scalar_5(5) as b;
SELECT scalar_1((select 5)) as b;
SELECT scalar_2((select 5)) as b;
SELECT scalar_3((select 5)) as b;
SELECT scalar_4((select 5)) as b;
SELECT scalar_5((select 5)) as b;
SELECT b FROM scalar_1(5) as b;
SELECT b FROM scalar_2(5) as b;
SELECT b FROM scalar_3(5);
SELECT x as b FROM scalar_4(5);
SELECT b FROM scalar_5(5) as b;
SELECT b FROM scalar_1((select 5)) as b;
SELECT b FROM scalar_2((select 5)) as b;
SELECT b FROM scalar_3((select 5));
SELECT x as b FROM scalar_4((select 5));
SELECT b FROM scalar_5((select 5)) as b;
-- end equivalent
-- ERROR cases for simple scalar functions
SELECT scalar_1((select 1, 2)); -- subquery returns multiple columns
SELECT scalar_1((select 1 union select 2)); -- subquery returns multiple rows
SELECT scalar_1(TABLE(select 1)); -- TableValue expression does not match type
SELECT scalar_5(TABLE(select 1)); -- TableValue shouldn't match "anyelement"
-- Normal scalar input / table output functions
-- begin equivalent */
SELECT row(a+5, b)::example from example;
SELECT scalar_tf_1(5);
SELECT scalar_tf_2(5);
SELECT scalar_tf_3(5);
SELECT scalar_tf_4(5);
SELECT scalar_tf_5(5);
SELECT scalar_tf_6(5);
SELECT scalar_tf_1((select 5));
SELECT scalar_tf_2((select 5));
SELECT scalar_tf_3((select 5));
SELECT scalar_tf_4((select 5));
SELECT scalar_tf_5((select 5));
SELECT scalar_tf_6((select 5));
-- end equivalent
-- begin equivalent
SELECT a+5 as a, b from example order by a, b;
SELECT * FROM scalar_tf_1(5) order by a, b;
SELECT * FROM scalar_tf_2(5) order by a, b;
SELECT * FROM scalar_tf_3(5) order by a, b;
SELECT * FROM scalar_tf_4(5) order by a, b;
SELECT * FROM scalar_tf_5(5) e(a int, b text) order by a, b;
SELECT * FROM scalar_tf_6(5) order by a, b;
SELECT * FROM scalar_tf_1((select 5)) order by a, b;
SELECT * FROM scalar_tf_2((select 5)) order by a, b;
SELECT * FROM scalar_tf_3((select 5)) order by a, b;
SELECT * FROM scalar_tf_4((select 5)) order by a, b;
SELECT * FROM scalar_tf_5((select 5)) e(a int, b text) order by a, b;
SELECT * FROM scalar_tf_6((select 5)) order by a, b;
/*
ERROR: Statement Error, writer gang busy: possible attempt to execute volatile function in unsupported context.
Fixed if we switch away from SQL language functions?
SELECT (scalar_tf_1(5)).*;
SELECT (scalar_tf_2(5)).*;
SELECT (scalar_tf_3(5)).*;
SELECT (scalar_tf_4(5)).*;
SELECT (scalar_tf_5(5)).*;
SELECT (scalar_tf_6(5)).*;
SELECT (scalar_tf_1((select 5))).*;
SELECT (scalar_tf_2((select 5))).*;
SELECT (scalar_tf_3((select 5))).*;
SELECT (scalar_tf_4((select 5))).*;
SELECT (scalar_tf_5((select 5))).*;
SELECT (scalar_tf_6((select 5))).*;
*/
-- end equivalent
-- ERROR cases for simple table output functions
SELECT scalar_tf_1((select 1, 2)); -- subquery returns multiple columns
SELECT scalar_tf_1((select 1 union select 2)); -- subquery returns multiple rows
SELECT scalar_tf_1(TABLE(select 1)); -- TableValue expression does not match type
SELECT scalar_tf_6(TABLE(select 1)); -- TableValue expression does not anyelement
-- Table Functions table input / table output
-- begin equivalent
SELECT * FROM example order by a, b;
SELECT * FROM multiset_2( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_3( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_4( TABLE( SELECT * from example) ) e(a int, b text) order by a, b;
SELECT * FROM multiset_6( TABLE( SELECT * from example) ) e(a int, b text) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER by a) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER by b) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER RANDOMLY) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY a, b SCATTER by a) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY b, a SCATTER by b) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example ORDER BY a, b SCATTER RANDOMLY) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example_r) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by a||b) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by b, a, a||b) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER RANDOMLY) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * from example_v) ) order by a, b;
SELECT * FROM multiset_5( TABLE( SELECT * FROM example WHERE a >= (SELECT min(a) FROM example))) order by a,b;
WITH cte AS (SELECT * FROM example) SELECT * FROM multiset_5( TABLE ( SELECT * FROM cte ) ) order by a, b;
WITH cte AS (SELECT * FROM example) SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM cte ) ) x, (SELECT count(*) FROM cte) y order by x.a, x.b;
SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM example ) ) x right join (SELECT 1) y on (true) order by x.a, x.b;
-- end equivalent
-- Table functions with quals
-- begin equivalent
SELECT * FROM example WHERE a = 2;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM example ) ) WHERE a = 2;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM example WHERE a = 2) ) ;
SELECT * FROM multiset_2( TABLE ( SELECT * FROM (SELECT * FROM example) example2 WHERE a = 2) );
SELECT * FROM multiset_2( TABLE ( SELECT * FROM (SELECT * FROM example WHERE a > 1) example2 ) ) WHERE a < 3;
-- end equivalent
-- Table Functions with a focused subplan plan
SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ) );
SELECT * FROM example_r WHERE (10, 'hello') in (SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ))) order by a, b;
-- Table Functions + Projection
-- begin equivalent
SELECT b FROM example order by b;
SELECT b FROM multiset_2( TABLE( SELECT * from example) ) order by b;
SELECT b FROM multiset_3( TABLE( SELECT * from example) ) order by b;
SELECT b FROM multiset_4( TABLE( SELECT * from example) ) e(a int, b text) order by b;
SELECT b FROM multiset_6( TABLE( SELECT * from example) ) e(a int, b text) order by b;
SELECT b FROM multiset_5( TABLE( SELECT * from example) ) order by b;
-- end equivalent
-- Different cases of joins in the subplan
-- begin equivalent
SELECT distinct a.a, b.b from example a, example b where a.a = b.a order by 1,2;
select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a, example b where a.a = b.a) ) order by 1,2;
select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a join example b using (a) ) ) order by 1,2;
-- end equivalent
-- Support for different backend code
-- this function always returns a single scalar null value
-- begin equivalent
SELECT null as a;
SELECT * FROM multiset_scalar_null( TABLE(SELECT 1) );
-- end equivalent
-- this function always returns a single scalar value of 42
-- begin equivalent
SELECT 42 as fortytwo;
SELECT * FROM multiset_scalar_value( TABLE(SELECT 42) ) fortytwo;
-- end equivalent
-- This function always returns a single "(1,Example)" tuple
-- begin equivalent
SELECT 1 as a, 'Example' as b;
SELECT * from multiset_scalar_tuple( TABLE(SELECT 1) );
-- end equivalent
-- ERROR: functions returning SETOF x cannot return null
SELECT * FROM multiset_setof_null( TABLE(SELECT 1) );
-- This function returns a setof values [1,2,3]
-- begin equivalent
SELECT a from generate_series(1,3) as a;
SELECT a from multiset_setof_value( TABLE(SELECT 1) ) as a;
-- end equivalent
-- ERROR: Table Functions do not currently support SFRM_Materialize
SELECT * FROM multiset_materialize_good( TABLE( SELECT * from example ) );
SELECT * FROM multiset_materialize_bad( TABLE( SELECT * from example ) );
-- name resolution rules should work correctly between scalar and anytable,
-- i.e. there cannot be any automatic conversion.
CREATE FUNCTION nameres(int) RETURNS int
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
SELECT * from nameres(5); -- should work
SELECT * from nameres(TABLE(SELECT 5)); -- should fail
CREATE FUNCTION nameres(anytable) RETURNS int
AS :'regress_dll', 'multiset_scalar_value' LANGUAGE C READS SQL DATA;
SELECT * from nameres(5); -- should work
SELECT * from nameres(TABLE(SELECT 5)); -- should work
DROP FUNCTION nameres(int);
SELECT * from nameres(5); -- should fail
SELECT * from nameres(TABLE(SELECT 5)); -- should work
DROP FUNCTION nameres(anytable);
SELECT * from nameres(5); -- should fail
SELECT * from nameres(TABLE(SELECT 5)); -- should fail
-- Error: anytable does NOT match anyelement
CREATE FUNCTION nameres(anyelement) returns int
AS $$ SELECT $1 $$ LANGUAGE SQL CONTAINS SQL;
SELECT * FROM nameres( TABLE( SELECT 1) ); -- should fail
DROP FUNCTION nameres(anyelement);
-- Must support resjunk scatter by clauses
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER BY a+1) );
SELECT * FROM multiset_5( TABLE( SELECT * FROM example SCATTER BY a+1) ) order by a,b;
-- Use of a select list table value expression subquery
-- begin equivalent
SELECT a FROM example ORDER BY a;
SELECT UNNEST(ARRAY(SELECT a FROM example)) ORDER BY 1;
SELECT UNNEST(ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example)))) ORDER BY 1;
-- end equivalent
-- Use of a where clause table value expression subquery
-- begin equivalent
SELECT * FROM example order by a, b;
SELECT * FROM example where (a,b) in (select * from example) order by a, b;
SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example) )) order by a, b;
SELECT * FROM multiset_5( TABLE(SELECT a, b from example)) where (a,b) in (select a,b from example) order by a, b;
-- end equivalent
-- Explain a couple interesting cases
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id) );
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id SCATTER BY dbid) );
explain SELECT * FROM multiset_5( TABLE (SELECT dbid, gpname FROM gp_id SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example) );
explain SELECT * from multiset_5( TABLE (SELECT * FROM example ORDER BY a limit 10 ) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY a, b LIMIT 10 SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a LIMIT 10 SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT * FROM example ORDER BY b, a LIMIT 10 SCATTER RANDOMLY) );
explain SELECT ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example)) order by a);
explain SELECT ARRAY(SELECT a FROM multiset_5( TABLE ( SELECT a, b from example order by a)));
explain SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example) ));
explain SELECT * FROM example where (a,b) in (select * from multiset_5( TABLE(SELECT a, b from example scatter by b) ));
explain SELECT * FROM multiset_5( TABLE(SELECT a, b from example)) where (a,b) in (select a,b from example);
explain SELECT * FROM multiset_5( TABLE(SELECT a, b from example scatter by b)) where (a,b) in (select a,b from example);
explain SELECT * FROM multiset_5( TABLE( SELECT * from example_r) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by a||b) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER by b, a, a||b) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example_r SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a / 2, b FROM example SCATTER BY a / 2) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example ORDER BY b, a SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example ORDER BY a, b SCATTER BY a) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example ORDER BY b, a SCATTER BY b) );
explain SELECT * FROM multiset_5( TABLE (SELECT DISTINCT a, b FROM example ORDER BY b, a LIMIT 2 SCATTER RANDOMLY) );
explain SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r ) );
explain SELECT * FROM example_r WHERE (10, 'hello') in (SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r )));
explain SELECT * FROM multiset_5( TABLE( SELECT * from example_v) );
explain SELECT * FROM multiset_5( TABLE( SELECT * FROM example WHERE a >= (SELECT min(a) FROM example)));
explain WITH cte AS (SELECT * FROM example) SELECT * FROM multiset_5( TABLE ( SELECT * FROM cte ) ) order by a, b;
explain WITH cte AS (SELECT * FROM example) SELECT x.* FROM multiset_5( TABLE ( SELECT * FROM cte ) ) x, (SELECT count(*) FROM cte) y order by x.a, x.b;
explain select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a, example b where a.a = b.a) ) order by 1,2;
explain select * from multiset_2( TABLE( SELECT distinct a.a, b.b from example a join example b using (a) ) ) order by 1,2;
explain select * from multiset_2( TABLE ( SELECT * FROM example WHERE a = 2 ) ) WHERE a = 2;
-- Not rescannable, should produce materialize node
explain SELECT x.* FROM multiset_5( TABLE ( SELECT 1 ) ) x right join (SELECT 1) y on (true);
-- Do an explain analyze while we are at it:
explain analyze SELECT * FROM multiset_5( TABLE( SELECT count(*)::integer, 'hello'::text from example_r scatter randomly) );
-- Test for an old bug in aggregate planning - this used to crash.
select count(*) from multiset_5(table(select * from example)) s ;
-- Test for 'select for update'
begin;
select count(*) from multiset_5(table(select * from example for update)) s ;
abort;
-- ===========================================
-- Test invalid use of table value expressions
-- ===========================================
-- ERROR cases for table functions over table value input
SELECT multiset_2( (SELECT * from example) ); -- not a valid subselect
SELECT multiset_2( (SELECT a from example) ); -- not a TableValue expression / no such function
SELECT multiset_2( TABLE(SELECT * from example) ); -- not in the from clause
SELECT multiset_3( TABLE(SELECT a from example) ); -- not in the from clause
SELECT multiset_5( TABLE(SELECT * from example) ) over (order by 1);
SELECT * from example where 3 = multiset_scalar_value( TABLE(select a from example) ); -- not in from
SELECT count(*) FROM example GROUP BY multiset_scalar_value( TABLE(select a from example) );
SELECT count(*) FROM example HAVING multiset_scalar_value( TABLE(select a from example) ) > 4;
SELECT sum(a) over (partition by multiset_scalar_value( TABLE(select a from example)) order by b) from example;
-- Error: multiset_5 expects (int, text) records as input
SELECT * FROM multiset_5( TABLE(SELECT 1) );
-- ERROR cases for simple TableValue Expressions (anything not a parameter to function)
SELECT TABLE( SELECT * from example );
SELECT ARRAY( TABLE( SELECT * from example) );
SELECT * FROM TABLE( SELECT * from example) );
SELECT * from example where TABLE( SELECT * from example) is not null;
SELECT count(*) FROM example GROUP BY TABLE(select a from example);
SELECT count(*) FROM example HAVING TABLE(select a from example) is not null;
SELECT sum(a) over (partition by TABLE(select * from example) order by b) from example;
-- ERROR raised internally by user code when we don't see an output tuple
-- that matches expectations
SELECT * FROM multiset_4( TABLE( SELECT 1::integer, 'hello'::text) ) as tf(a int);
-- ERROR no support for SELECT INTO in a TableValueExpression
SELECT * FROM multiset_2( TABLE( SELECT * INTO should_error FROM example ) );
-- MPP-14180 ERROR: We don't support TableValueExpressions with external correlations
SELECT * from example e1
WHERE a > (SELECT a FROM multiset_2( TABLE( SELECT * FROM example e2 WHERE e1.a=e2.a))) ;
-- ===========================================
-- Views and view display
-- ===========================================
CREATE VIEW v1 AS
SELECT * FROM multiset_2( TABLE( SELECT * from example) ) as tf;
SELECT * FROM v1 order by a, b;
\d+ v1
SELECT pg_get_viewdef('v1'::regclass);
CREATE VIEW v2 AS
SELECT * FROM multiset_2( TABLE( SELECT * from example SCATTER BY a||b, 5) ) as tf;
SELECT * FROM v2 order by a, b;
\d+ v2
SELECT pg_get_viewdef('v2'::regclass);
CREATE VIEW v3 AS
SELECT * FROM multiset_2( TABLE( SELECT * from example SCATTER RANDOMLY) ) as tf;
SELECT * FROM v3 order by a, b;
\d+ v3
SELECT pg_get_viewdef('v3'::regclass);
DROP view v1;
DROP view v2;
DROP view v3;
-- ========================================
-- Interaction with set returning functions
-- ========================================
CREATE FUNCTION multi_args(a anytable, x int) RETURNS SETOF example
AS :'regress_dll', 'multiset_example' LANGUAGE C READS SQL DATA;
-- In select list requires some extra setrefs logic in planning
SELECT *, generate_series(1,2) FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
-- Note the extra "ProjectSet" node when the srf is in the select list
explain SELECT *, generate_series(1,2) FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
explain SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
-- Error: don't support sets as arguments that are not TableValueExpr
SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), generate_series(1,2));
-- And use it with a CTAS as well
explain CREATE TABLE example_out AS SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
CREATE TABLE example_out AS SELECT * FROM multi_args( TABLE(SELECT 1::int, 'hello'::text), 2);
SELECT * FROM example_out;
DROP TABLE example_out;
-- ===============
-- Dropped Columns
-- ===============
DROP VIEW example_v;
ALTER TABLE example DROP column a;
-- ERROR: input tuple does not conform to expectations of multiset_5
SELECT * FROM multiset_5( TABLE( SELECT 'hello'::text) ) as tf;
-- However, this should work despite the output tupdesc having a dropped column.
SELECT * FROM multiset_5( TABLE( SELECT 1::integer, 'hello'::text) ) as tf;
-- ============================================
-- Currently on "C" is supported for "anytable"
-- ============================================
-- start_ignore
-- These may have been created by previous test
create language plpython3u;
-- end_ignore
CREATE FUNCTION tf_sql(anytable) returns int AS $$ SELECT 1 $$ language sql CONTAINS SQL;
CREATE FUNCTION tf_pgsql(anytable) returns int AS $$
BEGIN
RETURN 1;
END;
$$ language plpgsql NO SQL;
CREATE FUNCTION tf_python(anytable) returns int AS $$ return 1 $$ language plpython3u NO SQL;
-- ====================================
-- Test support of dynamic return types (aka the DESCRIBE callback)
-- ====================================
-- Create a function and a describe method
CREATE FUNCTION sessionize_describe(internal)
RETURNS internal
AS :'regress_dll', 'describe'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION sessionize(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA;
-- No dependency yet, all three queries should return 0 rows
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);
-- Should be able to drop the describe function, no ref yet
DROP FUNCTION sessionize_describe(internal);
-- Should fail, no such function
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- Recreate describe function
CREATE OR REPLACE FUNCTION sessionize_describe(internal)
RETURNS internal
AS :'regress_dll', 'describe'
LANGUAGE C READS SQL DATA;
-- Alter the existing function to add the describe callback
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- Observe the relationship now recorded in pg_proc_callback
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
-- Should fail due to dependency on sessionize
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
-- Should cascade to main function and cleanup pg_proc_callback entries.
DROP FUNCTION sessionize_describe(internal) CASCADE;
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);
-- Recreate both functions
CREATE OR REPLACE FUNCTION sessionize_describe(internal)
RETURNS internal
AS :'regress_dll', 'describe'
LANGUAGE C READS SQL DATA;
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- Check the dependency again, drop should fail
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
-- Alter existing function to remove the describe callback
CREATE OR REPLACE FUNCTION sessionize(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA;
-- Check the dependency again, drop should succeed
DROP FUNCTION sessionize_describe(internal);
SELECT * FROM pg_proc_callback where profnoid = 'sessionize'::regproc;
DROP FUNCTION sessionize(anytable, interval);
-- Shouldn't have any integrity constraint violations
SELECT * FROM pg_proc_callback where profnoid not in (select oid from pg_proc);
SELECT * FROM pg_proc_callback where procallback not in (select oid from pg_proc);
-- One more time, creating without using "OR REPLACE"
CREATE FUNCTION sessionize_describe(internal)
RETURNS internal
AS :'regress_dll', 'describe'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION sessionize_plain(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION sessionize_static(anytable, interval)
RETURNS TABLE(id integer, "time" timestamp, sessionnum integer)
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION sessionize_dynamic(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- Check catalog for the new functions, should only see sessionize_describe
SELECT * FROM pg_proc_callback
WHERE profnoid in (
'sessionize_plain'::regproc,
'sessionize_static'::regproc,
'sessionize_dynamic'::regproc
);
-- Plain without qualification fails
SELECT *
FROM sessionize_plain(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize
ORDER BY id, time; -- FAILS, no record type
-- Static with qualification fails
SELECT *
FROM sessionize_static(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- FAIL, double qualified
-- Describe with qualification fails
SELECT *
FROM sessionize_dynamic(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- FAIL, double qualified
-- Otherwise results should match
SELECT *
FROM sessionize_plain(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer)
ORDER BY id, time; -- SUCCESS, qualified record
SELECT *
FROM sessionize_static(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize
ORDER BY id, time; -- SUCCESS, qualified in function
SELECT *
FROM (SELECT sessionize.id, sessionize."time", sessionize.sessionnum
FROM sessionize_static(TABLE( SELECT history.id, history."time"
FROM history
ORDER BY history.id, history."time"
SCATTER BY history.id),
'00:01:00'::interval) sessionize
ORDER BY sessionize.id, sessionize."time") q
ORDER BY 1, 2; -- SUCCESS, qualified in function
SELECT *
FROM sessionize_dynamic(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize
ORDER BY id, time; -- SUCCESS, qualified in describe
-- Also test subqueries in non TABLE() parameters
SELECT *
FROM sessionize_dynamic(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
(select '1 minute'::interval) ) as sessionize
ORDER BY id, time;
-- ===================================
-- Dynamic type interaction with VIEWS
--
--
-- Because qualification is not guaranteed stable views over functions
-- with dynamic types are *not* supported.
--
-- There are multiple ways this could cause issues:
-- - DESCRIBE function is not immutable and could return different
-- descriptors at later points in time.
-- - DESCRIBE function could have the source code change resulting
-- in a different descriptor
-- - DESCRIBE function could be replaced with a different function
-- resulting in a different descriptor.
--
-- ===================================
CREATE VIEW not_supported
AS SELECT *
FROM sessionize_dynamic(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize; -- ERROR, view over dynamic typed function
-- The call is in a subquery, not the main query
CREATE VIEW not_supported
AS SELECT * FROM (
SELECT *
FROM sessionize_dynamic(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize
) subq; -- ERROR, view over dynamic typed function
--
-- An existing function that has views defined over it can not be allowed to
-- be altered to have a describe function for similar reasons outlined above.
--
CREATE VIEW supported
AS SELECT *
FROM sessionize_plain(
TABLE
(
SELECT id, time
FROM history
ORDER BY id, time
SCATTER BY id
),
'1 minute' ) as sessionize(id integer, "time" timestamp, sessionnum integer);
CREATE OR REPLACE FUNCTION sessionize_plain(anytable, interval)
RETURNS setof record
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe); -- ERROR: views exist
DROP VIEW supported;
-- ========================
-- The above describe is still pretty static, test a more dynamic describe function
-- ========================
CREATE FUNCTION project_describe(internal)
RETURNS internal
AS :'regress_dll', 'project_describe'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION project(anytable, integer)
RETURNS setof record
AS :'regress_dll', 'project'
LANGUAGE C READS SQL DATA
WITH (describe = project_describe);
SELECT * FROM project( TABLE( SELECT * FROM history ), 1) order by 1;
SELECT * FROM project( TABLE( SELECT * FROM history ), 2) order by 1;
-- Valid operations on results
SELECT id+1 FROM project( TABLE( SELECT * FROM history ), 1) order by 1;
SELECT extract(day from "time") FROM project( TABLE( SELECT * FROM history ), 2) order by 1;
SELECT * FROM project( TABLE( SELECT * FROM pg_am WHERE amname not in ('pax') ),
CASE 1 WHEN 2 THEN 1 ELSE GREATEST(1, COALESCE(1+1)) END);
SELECT * FROM project( TABLE( SELECT * FROM pg_am WHERE amname not in ('pax') ),
CASE WHEN 3 IS NOT NULL AND 1 IN (1, 2) THEN floor(NULLIF(2, 3))::int END);
-- ERROR: invalid operations demonstrating different return types
SELECT extract(day from id) FROM project( TABLE( SELECT * FROM history ), 1);
SELECT "time"+1 FROM project( TABLE( SELECT * FROM history ), 2);
-- ERROR: select columns projected out by the function
SELECT id FROM project( TABLE( SELECT * FROM history ), 2);
SELECT "time" FROM project( TABLE( SELECT * FROM history ), 1);
/* Invalid projection position */
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), NULL);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), -1);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), 0);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), 100);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), (ARRAY[2,3])[1]);
SELECT * FROM project( TABLE( SELECT * FROM pg_am ), (ROW(1, '')::example_r).a);
-- ========================
-- User data exmaple
-- ========================
CREATE FUNCTION ud_describe(internal) RETURNS internal
AS :'regress_dll', 'userdata_describe'
LANGUAGE C READS SQL DATA;
CREATE FUNCTION ud_project(anytable) RETURNS setof RECORD
AS :'regress_dll', 'userdata_project'
LANGUAGE C READS SQL DATA
WITH (describe = ud_describe);
SELECT * FROM ud_project( TABLE( SELECT * FROM history ) );
-- ========================
-- Passing input without modification
-- ========================
CREATE FUNCTION noop_project(anytable) RETURNS setof RECORD
AS :'regress_dll'
LANGUAGE C READS SQL DATA;
SELECT * FROM noop_project( TABLE( SELECT * FROM history ) ) AS s (id integer, time timestamp);
-- SCATTER BY vs MEDIAN
SELECT * FROM noop_project( TABLE( SELECT median(id) FROM history SCATTER BY median(id) ) ) AS s (med float);
SELECT * FROM noop_project( TABLE( SELECT median(id) FROM history SCATTER BY count(*) ) ) AS s (med float);
SELECT * FROM noop_project( TABLE( SELECT count(*) FROM history SCATTER BY median(id) ) ) AS s (cnt bigint);
SELECT * FROM noop_project( TABLE( SELECT count(*) FROM history GROUP BY time SCATTER BY median(id) ) ) AS s (cnt bigint);
-- ========================
-- More negative test cases
-- ========================
-- explicit return type not suitable for dynamic type resolution
CREATE FUNCTION x() returns int
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- explicit return type (setof) not suitable for dynamic type resolution
CREATE FUNCTION x() returns setof int
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- explicit return type (TABLE) not suitable for dynamic type resolution
CREATE FUNCTION x() returns TABLE(id integer, "time" timestamp, sessionnum integer)
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- explicit return type (OUT PARAMS) not suitable for dynamic type resolution
CREATE FUNCTION x(OUT id integer, OUT "time" timestamp, OUT sessionnum integer)
AS :'regress_dll', 'sessionize'
LANGUAGE C READS SQL DATA
WITH (describe = sessionize_describe);
-- ========================
-- Interaction with PREPARE
-- ========================
-- PASS: completely defined at prepare time
PREPARE p1 AS SELECT * FROM multiset_2( TABLE(SELECT * FROM example_r) ) order by a, b;
EXECUTE p1;
DEALLOCATE p1;
-- FAIL: can't pass anytable as prepare argument
PREPARE p2 AS SELECT * FROM multiset_2( $1 ) order by a, b;
-- This actually passes currently. You can't do anything useful with the prepared
-- statement though, as anytable's input function just throws an error.
PREPARE p3(anytable) AS SELECT * FROM multiset_5( $1 );
-- FAIL: $1 is not a constant
PREPARE p4 AS SELECT * FROM project( TABLE( SELECT * FROM pg_am ), $1);
PREPARE p5(integer) AS SELECT * FROM project( TABLE( SELECT * FROM pg_am ), $1);
-- PASS: We allow table functions with describe to be prepared. This can
-- theoretically require need for reparse/replan, but that requires more
-- general support of prepared statements in conjunction with a plan cache.
PREPARE p6 AS SELECT * FROM project( TABLE( SELECT * FROM example_r ), 2) ORDER BY 1;
EXECUTE p6;
-- PASS: Check that the prepared statement is re-planned when the underlying
-- table changes.
ALTER TABLE example_r ALTER COLUMN a TYPE numeric;
EXECUTE p6;
DEALLOCATE p6;
-- =======
-- Cleanup
-- =======
DROP SCHEMA table_function CASCADE;
SET search_path TO public;