diff --git a/License b/LICENSE similarity index 95% rename from License rename to LICENSE index 29cca7f..a4ae9a9 100644 --- a/License +++ b/LICENSE @@ -1,6 +1,6 @@ InfluxDB Foreign Data Wrapper for PostgreSQL -Copyright (c) 2018, TOSHIBA Corporation +Copyright (c) 2018 - 2020, TOSHIBA Corporation Copyright (c) 2011 - 2016, EnterpriseDB Corporation Permission to use, copy, modify, and distribute this software and its diff --git a/README.md b/README.md index 4086700..4ea233c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # InfluxDB Foreign Data Wrapper for PostgreSQL This PostgreSQL extension is a Foreign Data Wrapper (FDW) for InfluxDB. -The current version can work with PostgreSQL 9.6, 10 and 11. +The current version can work with PostgreSQL 9.6, 10, 11 and 12. Go version should be 1.10.4 or later. ## Installation @@ -57,8 +57,10 @@ SELECT * FROM t1; ## Features + +InfluxDB FDW supports pushed-down functions - WHERE clauses including timestamp, interval and `now()` functions are pushed down -- Some of aggregation are pushed down +- Some of aggregation are pushed down such as `influx_time` and `last` functions. These functions does not work on PostgreSQL 9. ## Limitations - INSERT, UPDATE and DELETE are not supported. @@ -67,9 +69,16 @@ Following limitations originate from data model and query language of InfluxDB. - Result sets have different number of rows depending on specified target list. For example, `SELECT field1 FROM t1` and `SELECT field2 FROM t1` returns different number of rows if the number of points with field1 and field2 are different in InfluxDB database. -- Currently `GROUP BY` works for only tag keys, not for field keys([#3](/../../issues/3)) - Timestamp precision may be lost because timestamp resolution of PostgreSQL is microseconds while that of InfluxDB is nanoseconds. - Conditions like `WHERE time + interval '1 day' < now()` do not work. Please use `WHERE time < now() - interval '1 day'`. +- `GROUP BY time` does not work (([#19](/../../issues/19)). +- Conditions have mix usage of aggregate function and arithmetic do not work ([#18](/../../issues/18)). +- `GROUP BY` does not support FIELD KEY having arithmetic ([#17](/../../issues/17)). +- `GROUP BY` only works with time and tag dimensions ([#16](/../../issues/16)). +- `GROUP BY` does not work with duplicated targets ([#15](/../../issues/15)). +- Aggregate functions with arithmetic in parentheses are not supported ([#14](/../../issues/14)). +- String comparisons do not work except `=` and `!=` ([#13](/../../issues/13)). +- `GROUP BY` works for only tag keys, not for field keys([#3](/../../issues/3)) When a query to foreing tables fails, you can find why it fails by seeing a query executed in InfluxDB with `EXPLAIN (VERBOSE)`. @@ -77,7 +86,8 @@ When a query to foreing tables fails, you can find why it fails by seeing a quer Opening issues and pull requests on GitHub are welcome. ## License -Copyright (c) 2018 - 2019, TOSHIBA Corporation +Copyright (c) 2018 - 2020, TOSHIBA Corporation + Copyright (c) 2011 - 2016, EnterpriseDB Corporation Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. diff --git a/deparse.c b/deparse.c index fa52125..ef7df24 100644 --- a/deparse.c +++ b/deparse.c @@ -28,7 +28,6 @@ #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" #include "optimizer/clauses.h" -#include "optimizer/var.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" #include "utils/builtins.h" diff --git a/expected/extra/aggregates.out b/expected/extra/aggregates.out index e1d6651..1b43d36 100644 --- a/expected/extra/aggregates.out +++ b/expected/extra/aggregates.out @@ -59,6 +59,7 @@ CREATE FOREIGN TABLE INT8_TBL2 ( CREATE FOREIGN TABLE INT4_TBL (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE INT4_TBL2 (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE INT4_TBL3 (f1 int4) SERVER influxdb_svr; +CREATE FOREIGN TABLE INT4_TBL4 (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE multi_arg_agg (a int, b int, c text) SERVER influxdb_svr; CREATE FOREIGN TABLE multi_arg_agg2 (a int, b int, c text) SERVER influxdb_svr; CREATE FOREIGN TABLE multi_arg_agg3 (a int, b int, c text) SERVER influxdb_svr; @@ -67,6 +68,8 @@ CREATE FOREIGN TABLE FLOAT8_TBL (f1 float8) SERVER influxdb_svr; -- -- AGGREGATES -- +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; SELECT avg(four) AS avg_1 FROM onek; avg_1 -------------------- @@ -204,16 +207,109 @@ SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); (1 row) -- verify correct results for null and NaN inputs --- select sum(null::int4) from generate_series(1,3); --- select sum(null::int8) from generate_series(1,3); --- select sum(null::numeric) from generate_series(1,3); --- select sum(null::float8) from generate_series(1,3); --- select avg(null::int4) from generate_series(1,3); --- select avg(null::int8) from generate_series(1,3); --- select avg(null::numeric) from generate_series(1,3); --- select avg(null::float8) from generate_series(1,3); --- select sum('NaN'::numeric) from generate_series(1,3); --- select avg('NaN'::numeric) from generate_series(1,3); +create foreign table generate_series1(a int) server influxdb_svr; +select sum(null::int4) from generate_series1; + sum +----- + +(1 row) + +select sum(null::int8) from generate_series1; + sum +----- + +(1 row) + +select sum(null::numeric) from generate_series1; + sum +----- + +(1 row) + +select sum(null::float8) from generate_series1; + sum +----- + +(1 row) + +select avg(null::int4) from generate_series1; + avg +----- + +(1 row) + +select avg(null::int8) from generate_series1; + avg +----- + +(1 row) + +select avg(null::numeric) from generate_series1; + avg +----- + +(1 row) + +select avg(null::float8) from generate_series1; + avg +----- + +(1 row) + +select sum('NaN'::numeric) from generate_series1; + sum +----- + NaN +(1 row) + +select avg('NaN'::numeric) from generate_series1; + avg +----- + NaN +(1 row) + +-- verify correct results for infinite inputs +create foreign table infinite1(id int, a text) server influxdb_svr; +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 1; + avg | var_pop +----------+--------- + Infinity | NaN +(1 row) + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 2; + avg | var_pop +----------+--------- + Infinity | NaN +(1 row) + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 3; + avg | var_pop +----------+--------- + Infinity | NaN +(1 row) + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 4; + avg | var_pop +-----+--------- + NaN | NaN +(1 row) + +-- test accuracy with a large input offset +create foreign table large_input1(id int, a int) server influxdb_svr; +SELECT avg(a::float8), var_pop(a::float8) +FROM large_input1 WHERE id=1; + avg | var_pop +-----------+--------- + 100000005 | 2.5 +(1 row) + +SELECT avg(a::float8), var_pop(a::float8) +FROM large_input1 WHERE id=2; +ERROR: value "7000000000005" is out of range for type integer -- SQL2003 binary aggregates SELECT regr_count(b, a) FROM aggtest; regr_count @@ -269,6 +365,86 @@ SELECT corr(b, a) FROM aggtest; 0.139634516517873 (1 row) +-- test accum and combine functions directly +CREATE FOREIGN TABLE regr_test1 (x float8, y float8) server influxdb_svr; +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (10,20,30,80); + count | sum | regr_sxx | sum | regr_syy | regr_sxy +-------+-----+----------+-----+----------+---------- + 0 | | | | | +(1 row) + +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1; + count | sum | regr_sxx | sum | regr_syy | regr_sxy +-------+-----+----------+------+----------+---------- + 5 | 240 | 6280 | 1490 | 95080 | 8680 +(1 row) + +CREATE FOREIGN TABLE float8_arr1 (id int, x text, y text) server influxdb_svr; +SELECT float8_accum(x::float8[], 100) FROM float8_arr1 WHERE id=1; + float8_accum +-------------- + {5,240,6280} +(1 row) + +SELECT float8_regr_accum(x::float8[], 200, 100) FROM float8_arr1 WHERE id=2; + float8_regr_accum +------------------------------ + {5,240,6280,1490,95080,8680} +(1 row) + +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (10,20,30); + count | sum | regr_sxx | sum | regr_syy | regr_sxy +-------+-----+----------+-----+----------+---------- + 0 | | | | | +(1 row) + +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (80,100); + count | sum | regr_sxx | sum | regr_syy | regr_sxy +-------+-----+----------+-----+----------+---------- + 0 | | | | | +(1 row) + +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=3; + float8_combine +---------------- + {3,60,200} +(1 row) + +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=4; + float8_combine +---------------- + {2,180,200} +(1 row) + +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=5; + float8_combine +---------------- + {5,240,6280} +(1 row) + +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=6; + float8_regr_combine +--------------------------- + {3,60,200,750,20000,2000} +(1 row) + +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=7; + float8_regr_combine +----------------------------- + {2,180,200,740,57800,-3400} +(1 row) + +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=8; + float8_regr_combine +------------------------------ + {5,240,6280,1490,95080,6880} +(1 row) + +-- test count, distinct SELECT count(four) AS cnt_1000 FROM onek; cnt_1000 ---------- @@ -283,7 +459,7 @@ SELECT count(DISTINCT four) AS cnt_4 FROM onek; select ten, count(*), sum(four) from onek group by ten order by ten; -ERROR: invalid input syntax for integer: "" +ERROR: invalid input syntax for type integer: "" select ten, count(four), sum(DISTINCT four) from onek group by ten order by ten; ERROR: influxdb_fdw : expected field argument in sum() @@ -372,77 +548,78 @@ LINE 4: where sum(distinct a.four + b.four) = b.four)... ^ -- Test handling of sublinks within outer-level aggregates. -- Per bug report from Daniel Grace. --- select --- (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) --- from tenk1 o; +-- this test +--select +-- (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) +--from tenk1 o; -- Test handling of Params within aggregate arguments in hashed aggregation. -- Per bug report from Jeevan Chalke. explain (verbose, costs off) -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss +select s1.a, ss.a, sm +from generate_series1 s1, + lateral (select s2.a, sum(s1.a + s2.a) sm + from generate_series1 s2 group by s2.a) ss order by 1, 2; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------ Sort - Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2))) - Sort Key: s1.s1, s2.s2 + Output: s1.a, s2.a, (sum((s1.a + s2.a))) + Sort Key: s1.a, s2.a -> Nested Loop - Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2))) - -> Function Scan on pg_catalog.generate_series s1 - Output: s1.s1 - Function Call: generate_series(1, 3) + Output: s1.a, s2.a, (sum((s1.a + s2.a))) + -> Foreign Scan on public.generate_series1 s1 + Output: s1.a + InfluxDB query: SELECT "a" FROM "generate_series1" -> HashAggregate - Output: s2.s2, sum((s1.s1 + s2.s2)) - Group Key: s2.s2 - -> Function Scan on pg_catalog.generate_series s2 - Output: s2.s2 - Function Call: generate_series(1, 3) + Output: s2.a, sum((s1.a + s2.a)) + Group Key: s2.a + -> Foreign Scan on public.generate_series1 s2 + Output: s2.a + InfluxDB query: SELECT "a" FROM "generate_series1" (14 rows) -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss +select s1.a, ss.a, sm +from generate_series1 s1, + lateral (select s2.a, sum(s1.a + s2.a) sm + from generate_series1 s2 group by s2.a) ss order by 1, 2; - s1 | s2 | sm -----+----+---- - 1 | 1 | 2 - 1 | 2 | 3 - 1 | 3 | 4 - 2 | 1 | 3 - 2 | 2 | 4 - 2 | 3 | 5 - 3 | 1 | 4 - 3 | 2 | 5 - 3 | 3 | 6 + a | a | sm +---+---+---- + 1 | 1 | 2 + 1 | 2 | 3 + 1 | 3 | 4 + 2 | 1 | 3 + 2 | 2 | 4 + 2 | 3 | 5 + 3 | 1 | 4 + 3 | 2 | 5 + 3 | 3 | 6 (9 rows) explain (verbose, costs off) -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; - QUERY PLAN -------------------------------------------------------------------- - Function Scan on pg_catalog.generate_series x +select array(select sum(x.a+y.a) s + from generate_series1 y group by y.a order by s) + from generate_series1 x; + QUERY PLAN +-------------------------------------------------------------------------- + Foreign Scan on public.generate_series1 x Output: (SubPlan 1) - Function Call: generate_series(1, 3) + InfluxDB query: SELECT "a" FROM "generate_series1" SubPlan 1 -> Sort - Output: (sum((x.x + y.y))), y.y - Sort Key: (sum((x.x + y.y))) + Output: (sum((x.a + y.a))), y.a + Sort Key: (sum((x.a + y.a))) -> HashAggregate - Output: sum((x.x + y.y)), y.y - Group Key: y.y - -> Function Scan on pg_catalog.generate_series y - Output: y.y - Function Call: generate_series(1, 3) + Output: sum((x.a + y.a)), y.a + Group Key: y.a + -> Foreign Scan on public.generate_series1 y + Output: y.a + InfluxDB query: SELECT "a" FROM "generate_series1" (13 rows) -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; +select array(select sum(x.a+y.a) s + from generate_series1 y group by y.a order by s) + from generate_series1 x; array --------- {2,3,4} @@ -502,19 +679,24 @@ FROM bitwise_test; -- test boolean aggregates -- -- first test all possible transition and final states +CREATE FOREIGN TABLE boolean1 (x1 BOOL, y1 BOOL , x2 BOOL, y2 BOOL, + x3 BOOL, y3 BOOL, x4 BOOL, y4 BOOL, + x5 BOOL, y5 BOOL, x6 BOOL, y6 BOOL, + x7 BOOL, y7 BOOL, x8 BOOL, y8 BOOL, + x9 BOOL, y9 BOOL) SERVER influxdb_svr; SELECT -- boolean and transitions -- null because strict - booland_statefunc(NULL, NULL) IS NULL AS "t", - booland_statefunc(TRUE, NULL) IS NULL AS "t", - booland_statefunc(FALSE, NULL) IS NULL AS "t", - booland_statefunc(NULL, TRUE) IS NULL AS "t", - booland_statefunc(NULL, FALSE) IS NULL AS "t", + booland_statefunc(x1, y1) IS NULL AS "t", + booland_statefunc(x2, y2) IS NULL AS "t", + booland_statefunc(x3, y3) IS NULL AS "t", + booland_statefunc(x4, y4) IS NULL AS "t", + booland_statefunc(x5, y5) IS NULL AS "t", -- and actual computations - booland_statefunc(TRUE, TRUE) AS "t", - NOT booland_statefunc(TRUE, FALSE) AS "t", - NOT booland_statefunc(FALSE, TRUE) AS "t", - NOT booland_statefunc(FALSE, FALSE) AS "t"; + booland_statefunc(x6, y6) AS "t", + NOT booland_statefunc(x7, y7) AS "t", + NOT booland_statefunc(x8, y8) AS "t", + NOT booland_statefunc(x9, y9) AS "t" FROM boolean1; t | t | t | t | t | t | t | t | t ---+---+---+---+---+---+---+---+--- t | t | t | t | t | t | t | t | t @@ -523,16 +705,16 @@ SELECT SELECT -- boolean or transitions -- null because strict - boolor_statefunc(NULL, NULL) IS NULL AS "t", - boolor_statefunc(TRUE, NULL) IS NULL AS "t", - boolor_statefunc(FALSE, NULL) IS NULL AS "t", - boolor_statefunc(NULL, TRUE) IS NULL AS "t", - boolor_statefunc(NULL, FALSE) IS NULL AS "t", + boolor_statefunc(x1, y1) IS NULL AS "t", + boolor_statefunc(x2, y2) IS NULL AS "t", + boolor_statefunc(x3, y3) IS NULL AS "t", + boolor_statefunc(x4, y4) IS NULL AS "t", + boolor_statefunc(x5, y5) IS NULL AS "t", -- actual computations - boolor_statefunc(TRUE, TRUE) AS "t", - boolor_statefunc(TRUE, FALSE) AS "t", - boolor_statefunc(FALSE, TRUE) AS "t", - NOT boolor_statefunc(FALSE, FALSE) AS "t"; + boolor_statefunc(x6, y6) AS "t", + boolor_statefunc(x7, y7) AS "t", + boolor_statefunc(x8, y8) AS "t", + NOT boolor_statefunc(x9, y9) AS "t" FROM boolean1; t | t | t | t | t | t | t | t | t ---+---+---+---+---+---+---+---+--- t | t | t | t | t | t | t | t | t @@ -1213,20 +1395,34 @@ ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argum LINE 1: select aggfns(distinct a,a,c order by a,b) from multi_arg_ag... ^ -- string_agg tests --- begin; --- delete from varchar_tbl; --- insert into varchar_tbl values ('aaaa'),('bbbb'),('cccc'); --- select string_agg(f1,',') from varchar_tbl; --- delete from varchar_tbl; --- insert into varchar_tbl values ('aaaa'),(null),('bbbb'),('cccc'); --- select string_agg(f1,',') from varchar_tbl; --- delete from varchar_tbl; --- insert into varchar_tbl values (null),(null),('bbbb'),('cccc'); --- select string_agg(f1,'AB') from varchar_tbl; --- delete from varchar_tbl; --- insert into varchar_tbl values (null),(null); --- select string_agg(f1,',') from varchar_tbl; --- rollback; +create foreign table string_agg1(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg2(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg3(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg4(a1 text, a2 text) server influxdb_svr; +select string_agg(a1,',') from string_agg1; + string_agg +---------------- + aaaa,bbbb,cccc +(1 row) + +select string_agg(a1,',') from string_agg2; + string_agg +---------------- + aaaa,bbbb,cccc +(1 row) + +select string_agg(a1,'AB') from string_agg3; + string_agg +------------ + bbbbABcccc +(1 row) + +select string_agg(a1,',') from string_agg4; + string_agg +------------ + +(1 row) + -- check some implicit casting cases, as per bug #5564 select string_agg(distinct f1, ',' order by f1) from varchar_tbl; -- ok string_agg @@ -1248,6 +1444,7 @@ select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl; - a,ab,abcd (1 row) +-- InfluxDB does not support binary data -- string_agg bytea tests -- create foreign table bytea_test_table(v bytea) server influxdb_svr; -- select string_agg(v, '') from bytea_test_table; @@ -1443,10 +1640,10 @@ select cume_dist(3) within group (order by f1) from INT4_TBL3; 0.875 (1 row) -select percent_rank(3) within group (order by f1) from INT4_TBL3; - percent_rank -------------------- - 0.571428571428571 +select percent_rank(3) within group (order by f1) from INT4_TBL4; + percent_rank +-------------- + 0.5 (1 row) select dense_rank(3) within group (order by f1) from INT4_TBL3; @@ -1477,8 +1674,14 @@ from tenk1; {{NULL,999,499},{749,249,NULL}} (1 row) --- select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by x) --- from generate_series(1,6) x; +create foreign table generate_series2 (a int) server influxdb_svr; +select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by a) +from generate_series2; + percentile_cont +------------------------------------------ + {1,6,2.25,4.75,3.5,6,2.5,2.6,2.75,2.9,3} +(1 row) + select ten, mode() within group (order by string4) from tenk1 group by ten; ten | mode -----+-------- @@ -1494,27 +1697,55 @@ select ten, mode() within group (order by string4) from tenk1 group by ten; 9 | VVVVxx (10 rows) -select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) -from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); +create foreign table percentile_disc1(x text) server influxdb_svr; +select percentile_disc(array[0.25,0.5,0.75]) within group (order by unnest) +from (select unnest(x::text[]) from percentile_disc1) y; percentile_disc ----------------- {fred,jill,jim} (1 row) -- check collation propagates up in suitable cases: +create foreign table pg_collation1 (x text) server influxdb_svr; select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) - from (values ('fred'),('jim')) v(x); + from pg_collation1; pg_collation_for ------------------ "POSIX" (1 row) +-- test ordered-set aggs using built-in support functions +create aggregate test_percentile_disc(float8 ORDER BY anyelement) ( + stype = internal, + sfunc = ordered_set_transition, + finalfunc = percentile_disc_final, + finalfunc_extra = true, + finalfunc_modify = read_write +); +create aggregate test_rank(VARIADIC "any" ORDER BY VARIADIC "any") ( + stype = internal, + sfunc = ordered_set_transition_multi, + finalfunc = rank_final, + finalfunc_extra = true, + hypothetical +); -- ordered-set aggs created with CREATE AGGREGATE --- select test_rank(3) within group (order by x) --- from (values (1),(1),(2),(2),(3),(3),(4)) v(x); --- select test_percentile_disc(0.5) within group (order by thousand) from tenk1; +create foreign table test_rank1 (x int) server influxdb_svr; +select test_rank(3) within group (order by x) from test_rank1; + test_rank +----------- + 5 +(1 row) + +select test_percentile_disc(0.5) within group (order by thousand) from tenk1; + test_percentile_disc +---------------------- + 499 +(1 row) + -- ordered-set aggs can't use ungrouped vars in direct args: -select rank(x) within group (order by x) from generate_series(1,5) x; +create foreign table generate_series3 (x int) server influxdb_svr; +select rank(x) within group (order by x) from generate_series3 x; ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: select rank(x) within group (order by x) from generate_serie... ^ @@ -1522,36 +1753,42 @@ DETAIL: Direct arguments of an ordered-set aggregate must use only grouped colu -- outer-level agg can't use a grouped arg of a lower level, either: select array(select percentile_disc(a) within group (order by x) from (values (0.3),(0.7)) v(a) group by a) - from generate_series(1,5) g(x); + from generate_series3; ERROR: outer-level aggregate cannot contain a lower-level variable in its direct arguments LINE 1: select array(select percentile_disc(a) within group (order b... ^ -- agg in the direct args is a grouping violation, too: -select rank(sum(x)) within group (order by x) from generate_series(1,5) x; +select rank(sum(x)) within group (order by x) from generate_series3 x; ERROR: aggregate function calls cannot be nested LINE 1: select rank(sum(x)) within group (order by x) from generate_... ^ -- hypothetical-set type unification and argument-count failures: --- select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); +select rank(3) within group (order by x) from pg_collation1; +ERROR: WITHIN GROUP types text and integer cannot be matched +LINE 1: select rank(3) within group (order by x) from pg_collation1; + ^ select rank(3) within group (order by stringu1,stringu2) from tenk1; ERROR: function rank(integer, name, name) does not exist LINE 1: select rank(3) within group (order by stringu1,stringu2) fro... ^ HINT: To use the hypothetical-set aggregate rank, the number of hypothetical direct arguments (here 1) must match the number of ordering columns (here 2). -select rank('fred') within group (order by x) from generate_series(1,5) x; -ERROR: invalid input syntax for integer: "fred" +select rank('fred') within group (order by x) from generate_series3 x; +ERROR: invalid input syntax for type integer: "fred" LINE 1: select rank('fred') within group (order by x) from generate_... ^ --- select rank('adam'::text collate "C") within group (order by x collate "POSIX") --- from (values ('fred'),('jim')) v(x); +select rank('adam'::text collate "C") within group (order by x collate "POSIX") + from pg_collation1; +ERROR: collation mismatch between explicit collations "C" and "POSIX" +LINE 1: ...adam'::text collate "C") within group (order by x collate "P... + ^ -- hypothetical-set type unification successes: -select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); +select rank('adam'::varchar) within group (order by x) from pg_collation1; rank ------ 1 (1 row) -select rank('3') within group (order by x) from generate_series(1,5) x; +select rank('3') within group (order by x) from generate_series3 x; rank ------ 3 @@ -1600,17 +1837,26 @@ select * from aggordview1 order by ten; (10 rows) drop view aggordview1; +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); -- variadic aggregates select least_agg(q1,q2) from int8_tbl; -ERROR: function least_agg(bigint, bigint) does not exist -LINE 1: select least_agg(q1,q2) from int8_tbl; - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. + least_agg +------------------- + -4567890123456789 +(1 row) + select least_agg(variadic array[q1,q2]) from int8_tbl; -ERROR: function least_agg(bigint[]) does not exist -LINE 1: select least_agg(variadic array[q1,q2]) from int8_tbl; - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. + least_agg +------------------- + -4567890123456789 +(1 row) + -- test aggregates with common transition functions share the same states begin work; create type avg_state as (total bigint, count bigint); @@ -1668,7 +1914,8 @@ create aggregate my_sum(int4) finalfunc = sum_finalfn ); -- aggregate state should be shared as aggs are the same. -select my_avg(one),my_avg(one) from (values(1),(3)) t(one); +create foreign table my_avg1 (one int) server influxdb_svr; +select my_avg(one),my_avg(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 my_avg | my_avg @@ -1677,7 +1924,7 @@ NOTICE: avg_transfn called with 3 (1 row) -- aggregate state should be shared as transfn is the same for both aggs. -select my_avg(one),my_sum(one) from (values(1),(3)) t(one); +select my_avg(one),my_sum(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 my_avg | my_sum @@ -1686,7 +1933,7 @@ NOTICE: avg_transfn called with 3 (1 row) -- same as previous one, but with DISTINCT, which requires sorting the input. -select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one); +select my_avg(distinct one),my_sum(distinct one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 my_avg | my_sum @@ -1695,7 +1942,7 @@ NOTICE: avg_transfn called with 3 (1 row) -- shouldn't share states due to the distinctness not matching. -select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one); +select my_avg(distinct one),my_sum(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 NOTICE: avg_transfn called with 1 @@ -1706,7 +1953,7 @@ NOTICE: avg_transfn called with 3 (1 row) -- shouldn't share states due to the filter clause not matching. -select my_avg(one) filter (where one > 1),my_sum(one) from (values(1),(3)) t(one); +select my_avg(one) filter (where one > 1),my_sum(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 NOTICE: avg_transfn called with 3 @@ -1716,7 +1963,8 @@ NOTICE: avg_transfn called with 3 (1 row) -- this should not share the state due to different input columns. -select my_avg(one),my_sum(two) from (values(1,2),(3,4)) t(one,two); +create foreign table my_avg2(one int, two int) server influxdb_svr; +select my_avg(one),my_sum(two) from my_avg2; NOTICE: avg_transfn called with 2 NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 4 @@ -1727,10 +1975,11 @@ NOTICE: avg_transfn called with 3 (1 row) -- exercise cases where OSAs share state +create foreign table percentile_cont1( a int) server influxdb_svr; select percentile_cont(0.5) within group (order by a), percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); +from percentile_cont1; percentile_cont | percentile_disc -----------------+----------------- 4 | 3 @@ -1739,7 +1988,7 @@ from (values(1::float8),(3),(5),(7)) t(a); select percentile_cont(0.25) within group (order by a), percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); +from percentile_cont1; percentile_cont | percentile_disc -----------------+----------------- 2.5 | 3 @@ -1749,7 +1998,7 @@ from (values(1::float8),(3),(5),(7)) t(a); select rank(4) within group (order by a), dense_rank(4) within group (order by a) -from (values(1),(3),(5),(7)) t(a); +from percentile_cont1; rank | dense_rank ------+------------ 3 | 3 @@ -1778,7 +2027,7 @@ create aggregate my_avg_init2(int4) initcond = '(4,0)' ); -- state should be shared if INITCONDs are matching -select my_sum_init(one),my_avg_init(one) from (values(1),(3)) t(one); +select my_sum_init(one),my_avg_init(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 my_sum_init | my_avg_init @@ -1787,7 +2036,7 @@ NOTICE: avg_transfn called with 3 (1 row) -- Varying INITCONDs should cause the states not to be shared. -select my_sum_init(one),my_avg_init2(one) from (values(1),(3)) t(one); +select my_sum_init(one),my_avg_init2(one) from my_avg1; NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 1 NOTICE: avg_transfn called with 3 @@ -1842,7 +2091,8 @@ create aggregate my_half_sum(int4) finalfunc = halfsum_finalfn ); -- Agg state should be shared even though my_sum has no finalfn -select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one); +create foreign table my_sum1(one int) server influxdb_svr; +select my_sum(one),my_half_sum(one) from my_sum1; NOTICE: sum_transfn called with 1 NOTICE: sum_transfn called with 2 NOTICE: sum_transfn called with 3 @@ -1920,23 +2170,28 @@ SET max_parallel_workers_per_gather = 4; SET enable_indexonlyscan = off; -- variance(int4) covers numeric_poly_combine -- sum(int8) covers int8_avg_combine -EXPLAIN (COSTS OFF) - SELECT variance(unique1::int4), sum(unique1::int8) FROM tenk1; - QUERY PLAN ------------------------------ +-- regr_count(float8, float8) covers int8inc_float8_float8 and aggregates with > 1 arg +EXPLAIN (COSTS OFF, VERBOSE) + SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) FROM tenk1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- Aggregate - -> Foreign Scan on tenk1 -(2 rows) + Output: variance(unique1), sum((unique1)::bigint), regr_count((unique1)::double precision, (unique1)::double precision) + -> Foreign Scan on public.tenk1 + Output: unique1 + InfluxDB query: SELECT "unique1" FROM "tenk" +(5 rows) -SELECT variance(unique1::int4), sum(unique1::int8) FROM tenk1; - variance | sum -----------------------+---------- - 8334166.666666666667 | 49995000 +SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) FROM tenk1; + variance | sum | regr_count +----------------------+----------+------------ + 8334166.666666666667 | 49995000 | 10000 (1 row) ROLLBACK; -- test coverage for dense_rank -SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM (VALUES (1),(1),(2),(2),(3),(3)) v(x) GROUP BY (x) ORDER BY 1; +create foreign table dense_rank1 (x int) server influxdb_svr; +SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM dense_rank1 GROUP BY (x) ORDER BY 1; dense_rank ------------ 1 @@ -1944,6 +2199,57 @@ SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM (VALUES (1),(1),(2),(2),(3), 1 (3 rows) +-- Ensure that the STRICT checks for aggregates does not take NULLness +-- of ORDER BY columns into account. See bug report around +-- 2a505161-2727-2473-7c46-591ed108ac52@email.cz +SELECT min(x ORDER BY y) FROM (VALUES(1, NULL)) AS d(x,y); + min +----- + 1 +(1 row) + +SELECT min(x ORDER BY y) FROM (VALUES(1, 2)) AS d(x,y); + min +----- + 1 +(1 row) + +-- check collation-sensitive matching between grouping expressions +select v||'a', case v||'a' when 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; + ?column? | case | count +----------+------+------- + aa | 1 | 1 + ba | 0 | 1 +(2 rows) + +select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; + ?column? | case | count +----------+------+------- + aa | 1 | 1 + ba | 0 | 1 +(2 rows) + +-- Make sure that generation of HashAggregate for uniqification purposes +-- does not lead to array overflow due to unexpected duplicate hash keys +-- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com +explain (costs off) + select 1 from tenk1 + where (hundred, thousand) in (select twothousand, twothousand from onek); + QUERY PLAN +------------------------------------------------------- + Hash Join + Hash Cond: (onek.twothousand = tenk1.hundred) + -> HashAggregate + Group Key: onek.twothousand, onek.twothousand + -> Foreign Scan on onek + -> Hash + -> Foreign Scan on tenk1 +(7 rows) + -- Clean up DO $d$ declare diff --git a/expected/extra/influxdb_fdw_post.out b/expected/extra/influxdb_fdw_post.out index c0f0642..5970fe2 100644 --- a/expected/extra/influxdb_fdw_post.out +++ b/expected/extra/influxdb_fdw_post.out @@ -16,6 +16,7 @@ CREATE USER MAPPING FOR public SERVER testserver1 OPTIONS (user 'value', passwor CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr OPTIONS (user 'user', password 'pass'); CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr2 OPTIONS (user 'user', password 'pass'); -- import time column as timestamp and text type +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE SCHEMA "S 1"; IMPORT FOREIGN SCHEMA public FROM SERVER influxdb_svr INTO "S 1"; -- =================================================================== @@ -27,6 +28,8 @@ CREATE FOREIGN TABLE ft1 ( c1 int NOT NULL, c2 int NOT NULL, c3 text, + -- c4 timestamptz, + -- c5 timestamp, c6 varchar(10), c7 char(10) default 'ft1', c8 text @@ -38,6 +41,8 @@ CREATE FOREIGN TABLE ft2 ( c2 int NOT NULL, cx int, c3 text, + -- c4 timestamptz, + -- c5 timestamp, c6 varchar(10), c7 char(10) default 'ft2', c8 text @@ -64,10 +69,45 @@ CREATE FOREIGN TABLE ft6 ( -- requiressl, krbsrvname and gsslib are omitted because they depend on -- configure options ALTER SERVER testserver1 OPTIONS ( + -- use_remote_estimate 'false', + -- updatable 'true', + -- fdw_startup_cost '123.456', + -- fdw_tuple_cost '0.123', + -- service 'value', + -- connect_timeout 'value', dbname 'value', host 'value', port 'value' + -- hostaddr 'value', + -- client_encoding 'value', + -- application_name 'value', + -- fallback_application_name 'value', + -- keepalives 'value', + -- keepalives_idle 'value', + -- keepalives_interval 'value', + -- tcp_user_timeout 'value', + -- requiressl 'value', + -- sslcompression 'value', + -- sslmode 'value', + -- sslcert 'value', + -- sslkey 'value', + -- sslrootcert 'value', + -- sslcrl 'value' + -- --requirepeer 'value', + -- krbsrvname 'value', + -- gsslib 'value', + -- replication 'value' ); +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: host, port, dbname +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: host, port, dbname +ALTER SERVER testserver1 OPTIONS (DROP extensions); +ERROR: option "extensions" not found ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (DROP user, DROP password); ALTER FOREIGN TABLE ft1 OPTIONS (table 'T1'); @@ -218,6 +258,14 @@ SELECT c3, time FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work (1 row) \set VERBOSITY default +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +WARNING: skipping "ft1" --- cannot analyze this foreign table +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +ERROR: invalid option "use_remote_estimate" +HINT: Valid options in this context are: table -- =================================================================== -- simple queries -- =================================================================== @@ -830,7 +878,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" (4 rows) -EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef QUERY PLAN ----------------------------------------------------------------------- Foreign Scan on public.ft1 t1 @@ -1132,9 +1180,32 @@ SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; 9 (1 row) +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +----------------------------------------------------------------------------------- + Limit + Output: "time", c1, c2, c3, c6, c7, c8 + -> Sort + Output: "time", c1, c2, c3, c6, c7, c8 + Sort Key: t1.c2 + -> Foreign Scan on public.ft1 t1 + Output: "time", c1, c2, c3, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" +(9 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + time | c1 | c2 | c3 | c6 | c7 | c8 +--------------------------+----+----+-------+----+------------+----- + Fri Jan 02 00:00:00 1970 | 1 | 1 | 00001 | 1 | 1 | foo +(1 row) + -- but let's put them in an extension ... ALTER EXTENSION influxdb_fdw ADD FUNCTION influxdb_fdw_abs(int); ALTER EXTENSION influxdb_fdw ADD OPERATOR === (int, int); +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- ... now they can be shipped EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = influxdb_fdw_abs(t1.c2); @@ -1172,9 +1243,37 @@ SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; 9 (1 row) +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +----------------------------------------------------------------------------------- + Limit + Output: "time", c1, c2, c3, c6, c7, c8 + -> Sort + Output: "time", c1, c2, c3, c6, c7, c8 + Sort Key: t1.c2 + -> Foreign Scan on public.ft1 t1 + Output: "time", c1, c2, c3, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" +(9 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + time | c1 | c2 | c3 | c6 | c7 | c8 +--------------------------+----+----+-------+----+------------+----- + Fri Jan 02 00:00:00 1970 | 1 | 1 | 00001 | 1 | 1 | foo +(1 row) + -- =================================================================== -- JOIN queries -- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +WARNING: skipping "ft4" --- cannot analyze this foreign table +ANALYZE ft5; +WARNING: skipping "ft5" --- cannot analyze this foreign table -- join two tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; @@ -1366,7 +1465,7 @@ SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE Output: t1.c1, t1.c2, ft5.c1, ft5.c2 Hash Cond: (t1.c1 = ft5.c1) -> Foreign Scan on public.ft4 t1 - Output: t1.c1, t1.c2, t1.c3 + Output: t1.c1, t1.c2 InfluxDB query: SELECT "c1", "c2" FROM "T3" WHERE (("c1" < 10)) -> Hash Output: ft5.c1, ft5.c2 @@ -1396,7 +1495,7 @@ SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE Hash Cond: (t1.c1 = ft5.c1) Filter: ((ft5.c1 < 10) OR (ft5.c1 IS NULL)) -> Foreign Scan on public.ft4 t1 - Output: t1.c1, t1.c2, t1.c3 + Output: t1.c1, t1.c2 InfluxDB query: SELECT "c1", "c2" FROM "T3" WHERE (("c1" < 10)) -> Hash Output: ft5.c1, ft5.c2 @@ -1471,7 +1570,7 @@ SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGH Output: t3.c3, t2.c2, t2.c1 Join Filter: (t2.c1 = t3.c1) -> Foreign Scan on public.ft4 t3 - Output: t3.c1, t3.c2, t3.c3 + Output: t3.c3, t3.c1 InfluxDB query: SELECT "c1", "c3" FROM "T3" -> Materialize Output: t2.c2, t2.c1 @@ -1555,7 +1654,7 @@ SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL Output: ft4.c1, ft5.c1 Hash Cond: (ft4.c1 = ft5.c1) -> Foreign Scan on public.ft4 - Output: ft4.c1, ft4.c2, ft4.c3 + Output: ft4.c1 InfluxDB query: SELECT "c1" FROM "T3" WHERE (("c1" >= 50)) AND (("c1" <= 60)) -> Hash Output: ft5.c1 @@ -1586,14 +1685,11 @@ SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELE -> Merge Full Join Output: 1 -> Foreign Scan on public.ft4 - Output: ft4.c1, ft4.c2, ft4.c3 InfluxDB query: SELECT * FROM "T3" WHERE (("c1" >= 50)) AND (("c1" <= 60)) -> Materialize - Output: ft5.c1, ft5.c2, ft5.c3 -> Foreign Scan on public.ft5 - Output: ft5.c1, ft5.c2, ft5.c3 InfluxDB query: SELECT * FROM "T4" WHERE (("c1" >= 50)) AND (("c1" <= 60)) -(12 rows) +(9 rows) SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; ?column? @@ -1626,7 +1722,7 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 Output: t2.c1, t3.c1 Hash Cond: (t3.c1 = t2.c1) -> Foreign Scan on public.ft5 t3 - Output: t3.c1, t3.c2, t3.c3 + Output: t3.c1 InfluxDB query: SELECT "c1" FROM "T4" -> Hash Output: t2.c1 @@ -1667,7 +1763,7 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 Hash Cond: (ft4_1.c1 = ft5.c1) Filter: ((ft4_1.c1 IS NULL) OR (ft4_1.c1 IS NOT NULL)) -> Foreign Scan on public.ft4 ft4_1 - Output: ft4_1.c1, ft4_1.c2, ft4_1.c3 + Output: ft4_1.c1 InfluxDB query: SELECT "c1" FROM "T3" WHERE (("c1" >= 50)) AND (("c1" <= 60)) -> Hash Output: ft5.c1 @@ -1756,7 +1852,7 @@ SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 a Output: t1.c1, t2.c1, t3.c1 Hash Cond: (t3.c1 = t2.c1) -> Foreign Scan on public.ft4 t3 - Output: t3.c1, t3.c2, t3.c3 + Output: t3.c1 InfluxDB query: SELECT "c1" FROM "T3" -> Hash Output: t1.c1, t2.c1 @@ -1764,7 +1860,7 @@ SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 a Output: t1.c1, t2.c1 Hash Cond: ((t2.c1 + 1) = t1.c1) -> Foreign Scan on public.ft5 t2 - Output: t2.c1, t2.c2, t2.c3 + Output: t2.c1 InfluxDB query: SELECT "c1" FROM "T4" -> Hash Output: t1.c1 @@ -1845,7 +1941,7 @@ SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT Output: t3.c3, t2.c2, t2.c1 Join Filter: (t2.c1 = t3.c1) -> Foreign Scan on public.ft4 t3 - Output: t3.c1, t3.c2, t3.c3 + Output: t3.c3, t3.c1 InfluxDB query: SELECT "c1", "c3" FROM "T3" -> Materialize Output: t2.c2, t2.c1 @@ -2153,6 +2249,12 @@ SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE InfluxDB query: SELECT "C1", "c3" FROM "T1" (14 rows) +-- skip, influxdb does not have option 'extensions' +-- ALTER SERVER influxdb_svr OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +-- EXPLAIN (VERBOSE, COSTS OFF) +-- SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- join two tables with FOR UPDATE clause -- tests whole-row reference for row marks EXPLAIN (VERBOSE, COSTS OFF) @@ -2314,7 +2416,7 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join in CTE EXPLAIN (VERBOSE, COSTS OFF) -WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; QUERY PLAN ------------------------------------------------------------------- Limit @@ -2342,7 +2444,7 @@ WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 Output: t.c1_1, t.c2_1, t.c1_3 (23 rows) -WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; c1_1 | c2_1 ------+------ 101 | 101 @@ -2460,7 +2562,7 @@ SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2 119 (10 rows) --- CROSS JOIN, not pushed down +-- CROSS JOIN can be pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; QUERY PLAN @@ -2729,7 +2831,7 @@ SELECT t1."C1" FROM "S 1"."T1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft -> Nested Loop Output: t1."C1" -> Foreign Scan on "S 1"."T1" t1 - Output: t1."time", t1."C1", t1.c2, t1.c3, t1.c6, t1.c7, t1.c8 + Output: t1."C1", t1.c2 InfluxDB query: SELECT "C1", "c2" FROM "T1" -> Unique Output: t2.c1, t3.c1 @@ -2749,7 +2851,6 @@ SELECT t1."C1" FROM "S 1"."T1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft InfluxDB query: SELECT "C1" FROM "T1" WHERE (("c2" = $1)) (26 rows) --- TODO SELECT t1."C1" FROM "S 1"."T1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C1" OFFSET 10 LIMIT 10; C1 ---- @@ -2830,6 +2931,7 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT (3 rows) -- join with nullable side with some columns with null values +-- UPDATE ft5 SET c3 = null where c1 % 9 = 0; EXPLAIN (VERBOSE, COSTS OFF) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; QUERY PLAN @@ -2861,39 +2963,47 @@ SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5 -- multi-way join involving multiple merge joins -- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; SET enable_nestloop TO false; SET enable_hashjoin TO false; EXPLAIN (VERBOSE, COSTS OFF) -SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 - AND ft1.c2 = ft5.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ LockRows - Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, ft1.*, ft2.*, ft4.*, ft5.* + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid -> Merge Join - Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, ft1.*, ft2.*, ft4.*, ft5.* + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid Merge Cond: (ft1.c2 = ft5.c1) -> Merge Join - Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.* + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid Merge Cond: (ft1.c2 = ft4.c1) - -> Sort - Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* - Sort Key: ft1.c2 - -> Merge Join + -> Merge Join + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Merge Cond: (local_tbl.c1 = ft1.c2) + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + -> Sort Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* - Merge Cond: (ft1.c1 = ft2.c1) - -> Sort - Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.* - Sort Key: ft1.c1 - -> Foreign Scan on public.ft1 + Sort Key: ft1.c2 + -> Merge Join + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* + Merge Cond: (ft1.c1 = ft2.c1) + -> Sort Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.* - InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" WHERE (("C1" < 100)) - -> Sort - Output: ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* - Sort Key: ft2.c1 - -> Foreign Scan on public.ft2 + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1."time", ft1.c1, ft1.c2, ft1.c3, ft1.c6, ft1.c7, ft1.c8, ft1.* + InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" WHERE (("C1" < 100)) + -> Sort Output: ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* - InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" WHERE (("C1" < 100)) + Sort Key: ft2.c1 + -> Foreign Scan on public.ft2 + Output: ft2."time", ft2.c1, ft2.c2, ft2.c3, ft2.c6, ft2.c7, ft2.c8, ft2.* + InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" WHERE (("C1" < 100)) -> Sort Output: ft4.c1, ft4.c2, ft4.c3, ft4.* Sort Key: ft4.c1 @@ -2906,26 +3016,27 @@ SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 -> Foreign Scan on public.ft5 Output: ft5.c1, ft5.c2, ft5.c3, ft5.* InfluxDB query: SELECT "c1", "c2", "c3" FROM "T4" -(38 rows) - -SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 - AND ft1.c2 = ft5.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; - time | c1 | c2 | c3 | c6 | c7 | c8 | time | c1 | c2 | c3 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 ---------------------------+----+----+-------+----+------------+-----+--------------------------+----+----+-------+----+------------+-----+----+----+--------+----+----+-------- - Tue Apr 07 00:00:00 1970 | 96 | 6 | 00096 | 6 | 6 | foo | Tue Apr 07 00:00:00 1970 | 96 | 6 | 00096 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Mon Feb 16 00:00:00 1970 | 46 | 6 | 00046 | 6 | 6 | foo | Mon Feb 16 00:00:00 1970 | 46 | 6 | 00046 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Tue Jan 27 00:00:00 1970 | 26 | 6 | 00026 | 6 | 6 | foo | Tue Jan 27 00:00:00 1970 | 26 | 6 | 00026 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Thu Feb 26 00:00:00 1970 | 56 | 6 | 00056 | 6 | 6 | foo | Thu Feb 26 00:00:00 1970 | 56 | 6 | 00056 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Sun Mar 08 00:00:00 1970 | 66 | 6 | 00066 | 6 | 6 | foo | Sun Mar 08 00:00:00 1970 | 66 | 6 | 00066 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Sat Jan 17 00:00:00 1970 | 16 | 6 | 00016 | 6 | 6 | foo | Sat Jan 17 00:00:00 1970 | 16 | 6 | 00016 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Wed Mar 18 00:00:00 1970 | 76 | 6 | 00076 | 6 | 6 | foo | Wed Mar 18 00:00:00 1970 | 76 | 6 | 00076 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Sat Mar 28 00:00:00 1970 | 86 | 6 | 00086 | 6 | 6 | foo | Sat Mar 28 00:00:00 1970 | 86 | 6 | 00086 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Wed Jan 07 00:00:00 1970 | 6 | 6 | 00006 | 6 | 6 | foo | Wed Jan 07 00:00:00 1970 | 6 | 6 | 00006 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 - Fri Feb 06 00:00:00 1970 | 36 | 6 | 00036 | 6 | 6 | foo | Fri Feb 06 00:00:00 1970 | 36 | 6 | 00036 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 +(43 rows) + +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + time | c1 | c2 | c3 | c6 | c7 | c8 | time | c1 | c2 | c3 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3 +--------------------------+----+----+-------+----+------------+-----+--------------------------+----+----+-------+----+------------+-----+----+----+--------+----+----+--------+----+----+------ + Tue Apr 07 00:00:00 1970 | 96 | 6 | 00096 | 6 | 6 | foo | Tue Apr 07 00:00:00 1970 | 96 | 6 | 00096 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Mon Feb 16 00:00:00 1970 | 46 | 6 | 00046 | 6 | 6 | foo | Mon Feb 16 00:00:00 1970 | 46 | 6 | 00046 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Tue Jan 27 00:00:00 1970 | 26 | 6 | 00026 | 6 | 6 | foo | Tue Jan 27 00:00:00 1970 | 26 | 6 | 00026 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Thu Feb 26 00:00:00 1970 | 56 | 6 | 00056 | 6 | 6 | foo | Thu Feb 26 00:00:00 1970 | 56 | 6 | 00056 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Sun Mar 08 00:00:00 1970 | 66 | 6 | 00066 | 6 | 6 | foo | Sun Mar 08 00:00:00 1970 | 66 | 6 | 00066 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Sat Jan 17 00:00:00 1970 | 16 | 6 | 00016 | 6 | 6 | foo | Sat Jan 17 00:00:00 1970 | 16 | 6 | 00016 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Wed Mar 18 00:00:00 1970 | 76 | 6 | 00076 | 6 | 6 | foo | Wed Mar 18 00:00:00 1970 | 76 | 6 | 00076 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Sat Mar 28 00:00:00 1970 | 86 | 6 | 00086 | 6 | 6 | foo | Sat Mar 28 00:00:00 1970 | 86 | 6 | 00086 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Wed Jan 07 00:00:00 1970 | 6 | 6 | 00006 | 6 | 6 | foo | Wed Jan 07 00:00:00 1970 | 6 | 6 | 00006 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + Fri Feb 06 00:00:00 1970 | 36 | 6 | 00036 | 6 | 6 | foo | Fri Feb 06 00:00:00 1970 | 36 | 6 | 00036 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 (10 rows) RESET enable_nestloop; RESET enable_hashjoin; +DROP TABLE local_tbl; -- check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER influxdb_svr OPTIONS (user 'user', password 'pass'); @@ -3121,7 +3232,7 @@ select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (ran Output: count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), c2 Group Key: ft1.c2 -> Foreign Scan on public.ft1 - Output: c6, c1, c2 + Output: c2, c6, c1 InfluxDB query: SELECT "C1", "c2", "c6" FROM "T1" WHERE (("c2" < 5)) (11 rows) @@ -3135,6 +3246,31 @@ select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (ran 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 (5 rows) +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), (((sum(c1)) * ((random() <= '1'::double precision))::integer)), c2 + -> Result + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + -> Sort + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), c2 + Sort Key: (count(ft1.c6)), (sum(ft1.c1)) + -> HashAggregate + Output: count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), c2 + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + InfluxDB query: SELECT "C1", "c2", "c6" FROM "T1" WHERE (("c2" < 5)) +(13 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + -- Aggregate is not pushed down as aggregation contains random() explain (verbose, costs off) select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; @@ -3348,10 +3484,49 @@ select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 Group Key: ft1.c2 Filter: (avg((ft1.c1 * ((random() <= '1'::double precision))::integer)) > '100'::numeric) -> Foreign Scan on public.ft1 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" (10 rows) +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +---------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + InfluxDB query: SELECT sum("C1") FROM "T1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum +(5 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +----------------------------------------------- + GroupAggregate + Output: ($0), sum(ft1.c1) + Group Key: $0 + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: $0, ft1.c1 + InfluxDB query: SELECT "C1" FROM "T1" +(8 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregate, same column used to order explain (verbose, costs off) @@ -3533,7 +3708,7 @@ select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: ft1.c2 -> Foreign Scan on public.ft1 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" (9 rows) @@ -3561,7 +3736,7 @@ select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 f Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: ft1.c2 -> Foreign Scan on public.ft1 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("c2" = 6)) (6 rows) @@ -3638,7 +3813,7 @@ select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 ord Output: sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision)), c2 Group Key: ft1.c2 -> Foreign Scan on public.ft1 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" (9 rows) @@ -3707,21 +3882,21 @@ select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 (1 row) -- User defined function for user defined aggregate, VARIADIC -create function least_accum(anyelement, variadic anyarray) +create function least_accum1(anyelement, variadic anyarray) returns anyelement language sql as 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; -create aggregate least_agg(variadic items anyarray) ( - stype = anyelement, sfunc = least_accum +create aggregate least_agg1(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum1 ); -- Disable hash aggregation for plan stability. set enable_hashagg to false; -- Not pushed down due to user defined aggregate explain (verbose, costs off) -select c2, least_agg(c1) from ft1 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 group by c2 order by c2; QUERY PLAN ----------------------------------------------------------- GroupAggregate - Output: c2, least_agg(VARIADIC ARRAY[c1]) + Output: c2, least_agg1(VARIADIC ARRAY[c1]) Group Key: ft1.c2 -> Sort Output: c2, c1 @@ -3732,15 +3907,15 @@ select c2, least_agg(c1) from ft1 group by c2 order by c2; (9 rows) -- Add function and aggregate into extension -alter extension influxdb_fdw add function least_accum(anyelement, variadic anyarray); -alter extension influxdb_fdw add aggregate least_agg(variadic items anyarray); +alter extension influxdb_fdw add function least_accum1(anyelement, variadic anyarray); +alter extension influxdb_fdw add aggregate least_agg1(variadic items anyarray); -- Now aggregate will be pushed. Aggregate will display VARIADIC argument. explain (verbose, costs off) -select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 where c2 < 100 group by c2 order by c2; QUERY PLAN -------------------------------------------------------------------------------- GroupAggregate - Output: c2, least_agg(VARIADIC ARRAY[c1]) + Output: c2, least_agg1(VARIADIC ARRAY[c1]) Group Key: ft1.c2 -> Sort Output: c2, c1 @@ -3750,31 +3925,31 @@ select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("c2" < 100)) (9 rows) -select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; - c2 | least_agg -----+----------- - 0 | 10 - 1 | 1 - 2 | 2 - 3 | 3 - 4 | 4 - 5 | 5 - 6 | 6 - 7 | 7 - 8 | 8 - 9 | 9 +select c2, least_agg1(c1) from ft1 where c2 < 100 group by c2 order by c2; + c2 | least_agg1 +----+------------ + 0 | 10 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 (10 rows) -- Remove function and aggregate from extension -alter extension influxdb_fdw drop function least_accum(anyelement, variadic anyarray); -alter extension influxdb_fdw drop aggregate least_agg(variadic items anyarray); +alter extension influxdb_fdw drop function least_accum1(anyelement, variadic anyarray); +alter extension influxdb_fdw drop aggregate least_agg1(variadic items anyarray); -- Not pushed down as we have dropped objects from extension. explain (verbose, costs off) -select c2, least_agg(c1) from ft1 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 group by c2 order by c2; QUERY PLAN ----------------------------------------------------------- GroupAggregate - Output: c2, least_agg(VARIADIC ARRAY[c1]) + Output: c2, least_agg1(VARIADIC ARRAY[c1]) Group Key: ft1.c2 -> Sort Output: c2, c1 @@ -3786,8 +3961,8 @@ select c2, least_agg(c1) from ft1 group by c2 order by c2; -- Cleanup reset enable_hashagg; -drop aggregate least_agg(variadic items anyarray); -drop function least_accum(anyelement, variadic anyarray); +drop aggregate least_agg1(variadic items anyarray); +drop function least_accum1(anyelement, variadic anyarray); -- Testing USING OPERATOR() in ORDER BY within aggregate. -- For this, we need user defined operators along with operator family and -- operator class. Create those and then add them in extension. Note that @@ -3826,10 +4001,13 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 Group Key: ft2.c2 -> Foreign Scan on public.ft2 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("C1" < 100)) AND (("c2" = 6)) (6 rows) +-- Update local stats on ft2 +ANALYZE ft2; +WARNING: skipping "ft2" --- cannot analyze this foreign table -- Add into extension alter extension influxdb_fdw add operator class my_op_class using btree; alter extension influxdb_fdw add function my_op_cmp(a int, b int); @@ -3846,7 +4024,7 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 Group Key: ft2.c2 -> Foreign Scan on public.ft2 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("C1" < 100)) AND (("c2" = 6)) (6 rows) @@ -3872,7 +4050,7 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 Group Key: ft2.c2 -> Foreign Scan on public.ft2 - Output: c1, c2 + Output: c2, c1 InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("C1" < 100)) AND (("c2" = 6)) (6 rows) @@ -3935,7 +4113,7 @@ select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x w (23 rows) select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; -ERROR: invalid input syntax for integer: "" +ERROR: invalid input syntax for type integer: "" -- FULL join with IS NULL check in HAVING explain (verbose, costs off) select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; @@ -3985,7 +4163,7 @@ select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 betwee Output: ft4.c1, ft5.c1 Hash Cond: (ft4.c1 = ft5.c1) -> Foreign Scan on public.ft4 - Output: ft4.c1, ft4.c2, ft4.c3 + Output: ft4.c1 InfluxDB query: SELECT "c1" FROM "T3" WHERE (("c1" >= 50)) AND (("c1" <= 60)) -> Hash Output: ft5.c1 @@ -4032,7 +4210,7 @@ select c2, sum from "S 1"."T1" t1, lateral (select sum(t2.c1 + t1."C1") sum from -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on "S 1"."T1" t1 - Output: t1."time", t1."C1", t1.c2, t1.c3, t1.c6, t1.c7, t1.c8 + Output: t1.c2, t1."C1" InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("c2" < 3)) AND (("C1" < 100)) -> Subquery Scan on qry Output: qry.sum, t2.c1 @@ -4056,6 +4234,65 @@ select c2, sum from "S 1"."T1" t1, lateral (select sum(t2.c1 + t1."C1") sum from (2 rows) reset enable_hashagg; +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T1" AS ref_0, + LATERAL ( + SELECT ref_0."C1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C1"; + QUERY PLAN +------------------------------------------------------------------------------------- + Sort + Output: ref_0.c2, ref_0."C1", (ref_0.c2), ref_1.c3, ref_0."C1" + Sort Key: ref_0."C1" + -> Nested Loop + Output: ref_0.c2, ref_0."C1", (ref_0.c2), ref_1.c3, ref_0."C1" + -> Nested Loop + Output: ref_0.c2, ref_0."C1", ref_1.c3, (ref_0.c2) + -> Foreign Scan on "S 1"."T1" ref_0 + Output: ref_0.c2, ref_0."C1" + InfluxDB query: SELECT "C1", "c2" FROM "T1" WHERE (("C1" < 10)) + -> Foreign Scan on public.ft1 ref_1 + Output: ref_1.c3, ref_0.c2 + InfluxDB query: SELECT "c3" FROM "T1" WHERE (("c3" = '00001')) + -> Materialize + Output: ref_3.c3 + -> Foreign Scan on public.ft2 ref_3 + Output: ref_3.c3 + InfluxDB query: SELECT "c3" FROM "T1" WHERE (("c3" = '00001')) +(18 rows) + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T1" AS ref_0, + LATERAL ( + SELECT ref_0."C1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C1"; + c2 | c1 | c2 | c3 +----+----+----+------- + 1 | 1 | 1 | 00001 + 2 | 2 | 2 | 00001 + 3 | 3 | 3 | 00001 + 4 | 4 | 4 | 00001 + 5 | 5 | 5 | 00001 + 6 | 6 | 6 | 00001 + 7 | 7 | 7 | 00001 + 8 | 8 | 8 | 00001 + 9 | 9 | 9 | 00001 +(9 rows) + -- Check with placeHolderVars explain (verbose, costs off) select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); @@ -4068,7 +4305,7 @@ select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2. Inner Unique: true Join Filter: ((ft4.c1)::numeric <= q.b) -> Foreign Scan on public.ft4 - Output: ft4.c1, ft4.c2, ft4.c3 + Output: ft4.c1 InfluxDB query: SELECT "c1" FROM "T3" -> Materialize Output: q.a, q.b @@ -4237,7 +4474,7 @@ select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 gr (11 rows) select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; -ERROR: invalid input syntax for integer: "" +ERROR: invalid input syntax for type integer: "" explain (verbose, costs off) select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; QUERY PLAN @@ -4491,6 +4728,9 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" WHERE (("C1" = "c2")) (3 rows) +-- influxdb doesnot support INSERT +-- PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T1" RENAME TO "T0"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table 'T0'); EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; @@ -4506,22 +4746,9 @@ EXECUTE st6; ------+----+----+----+----+----+---- (0 rows) +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T0" RENAME TO "T1"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table 'T1'); -EXECUTE st6; - time | c1 | c2 | c3 | c6 | c7 | c8 ---------------------------+----+----+-------+----+------------+----- - Fri Jan 02 00:00:00 1970 | 1 | 1 | 00001 | 1 | 1 | foo - Sat Jan 03 00:00:00 1970 | 2 | 2 | 00002 | 2 | 2 | foo - Sun Jan 04 00:00:00 1970 | 3 | 3 | 00003 | 3 | 3 | foo - Mon Jan 05 00:00:00 1970 | 4 | 4 | 00004 | 4 | 4 | foo - Tue Jan 06 00:00:00 1970 | 5 | 5 | 00005 | 5 | 5 | foo - Wed Jan 07 00:00:00 1970 | 6 | 6 | 00006 | 6 | 6 | foo - Thu Jan 08 00:00:00 1970 | 7 | 7 | 00007 | 7 | 7 | foo - Fri Jan 09 00:00:00 1970 | 8 | 8 | 00008 | 8 | 8 | foo - Sat Jan 10 00:00:00 1970 | 9 | 9 | 00009 | 9 | 9 | foo -(9 rows) - PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; QUERY PLAN @@ -4534,12 +4761,11 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; InfluxDB query: SELECT "C1", "c2", "c3" FROM "T1" (6 rows) -EXECUTE st8; - count -------- - 9 -(1 row) - +-- Skip, Influxdb does not support extensions +-- ALTER SERVER loopback OPTIONS (DROP extensions); +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +-- EXECUTE st8; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- cleanup DEALLOCATE st1; DEALLOCATE st2; @@ -4547,6 +4773,7 @@ DEALLOCATE st3; DEALLOCATE st4; DEALLOCATE st5; DEALLOCATE st6; +-- DEALLOCATE st7; DEALLOCATE st8; -- System columns, except ctid and oid, should not be sent to remote EXPLAIN (VERBOSE, COSTS OFF) @@ -4584,6 +4811,21 @@ SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; ft1 | Fri Jan 02 00:00:00 1970 | 1 | 1 | 00001 | 1 | 1 | foo (1 row) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + QUERY PLAN +----------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: "time", c1, c2, c3, c6, c7, c8 + Filter: (t1.ctid = '(0,2)'::tid) + InfluxDB query: SELECT "C1", "c2", "c3", "c6", "c7", "c8" FROM "T1" +(4 rows) + +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + time | c1 | c2 | c3 | c6 | c7 | c8 +------+----+----+----+----+----+---- +(0 rows) + EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid, * FROM ft1 t1 LIMIT 1; QUERY PLAN @@ -4625,15 +4867,15 @@ DROP FUNCTION f_test(int); -- =================================================================== ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; SELECT * FROM ft1 WHERE c1 = 1; -- ERROR -ERROR: invalid input syntax for integer: "foo" +ERROR: invalid input syntax for type integer: "foo" SELECT ft1.c1, ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR -ERROR: invalid input syntax for integer: "foo" +ERROR: invalid input syntax for type integer: "foo" SELECT ft1.c1, ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR -ERROR: invalid input syntax for integer: "foo" +ERROR: invalid input syntax for type integer: "foo" SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR -ERROR: invalid input syntax for integer: "foo" +ERROR: invalid input syntax for type integer: "foo" ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; -SELECT * FROM ft1 WHERE c1 = 1; +SELECT * FROM ft1 WHERE c1 = 1; -- Should work time | c1 | c2 | c3 | c6 | c7 | c8 --------------------------+----+----+-------+----+------------+----- Fri Jan 02 00:00:00 1970 | 1 | 1 | 00001 | 1 | 1 | foo @@ -4790,6 +5032,144 @@ explain (verbose, costs off) select * from ft3 f, loct3 l InfluxDB query: SELECT "f1", "f2", "f3" FROM "loct3" (9 rows) +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +-- Skip +-- EXPLAIN (verbose, costs off) +-- INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +-- INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +-- INSERT INTO ft2 (c1,c2,c3) +-- VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down +-- UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down +-- UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT +-- FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down +-- UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT +-- FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down +-- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down +-- DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +-- SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; +-- EXPLAIN (verbose, costs off) +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +-- UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +-- DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; +-- Test UPDATE/DELETE with RETURNING on a three-table join +-- INSERT INTO ft2 (c1,c2,c3) +-- SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'foo' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 +-- RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +-- UPDATE ft2 SET c3 = 'foo' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 +-- RETURNING ft2, ft2.*, ft4, ft4.*; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 +-- USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 +-- RETURNING 100; -- can be pushed down +-- DELETE FROM ft2 +-- USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 +-- RETURNING 100; +-- DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +-- ALTER SERVER loopback OPTIONS (DROP extensions); +-- INSERT INTO ft2 (c1,c2,c3) +-- SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +-- UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'baz' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 +-- RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +-- UPDATE ft2 SET c3 = 'baz' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 +-- RETURNING ft2.*, ft4.*, ft5.*; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 +-- USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 +-- RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +-- DELETE FROM ft2 +-- USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 +-- RETURNING ft2.c1, ft2.c2, ft2.c3; +-- DELETE FROM ft2 WHERE ft2.c1 > 2000; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- Test that trigger on remote table works as expected +-- CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +-- BEGIN +-- NEW.c3 = NEW.c3 || '_trig_update'; +-- RETURN NEW; +-- END; +-- $$ LANGUAGE plpgsql; +-- CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE +-- ON "S 1"."T1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; +-- INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; +-- UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; +-- Test errors thrown on remote side during update +-- ALTER TABLE "S 1"."T1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); +-- INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +-- INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +-- UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +-- Test savepoint/rollback behavior +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; +-- begin; +-- update ft2 set c2 = 42 where c2 = 0; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s1; +-- update ft2 set c2 = 44 where c2 = 4; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s1; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s2; +-- update ft2 set c2 = 46 where c2 = 6; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- rollback to savepoint s2; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s2; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s3; +-- update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +-- rollback to savepoint s3; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s3; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- none of the above is committed yet remotely +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; +-- commit; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; +-- VACUUM ANALYZE "S 1"."T1"; -- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs -- FIRST behavior here. -- ORDER BY DESC NULLS LAST options @@ -4879,18 +5259,1527 @@ SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; Tue Sep 08 00:00:00 1970 | 250 | 0 | 00250 | 0 | 0 | foo (10 rows) +-- =================================================================== +-- test check constraints +-- =================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + InfluxDB query: SELECT count(*) FROM "T1" WHERE (("c2" < 0)) +(3 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- +(0 rows) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +-- INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +-- UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +----------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + InfluxDB query: SELECT count(*) FROM "T1" WHERE (("c2" >= 0)) +(3 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 1000 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +-- INSERT INTO ft1(c1, c2) VALUES(1111, 2); +-- UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +-- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() +RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; +RETURN NEW; END$$ LANGUAGE plpgsql; +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger +BEFORE INSERT OR UPDATE ON base_tbl +FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +-- skip, Influxdb does not support INSERT. However, we keep this test case +-- to test locally +--CREATE FOREIGN TABLE foreign_tbl (a int, b int) +-- SERVER influxdb_svr OPTIONS (table 'base_tbl'); +CREATE VIEW rw_view AS SELECT * FROM base_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + View "public.rw_view" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+---------+-----------+----------+---------+---------+------------- + a | integer | | | | plain | + b | integer | | | | plain | +View definition: + SELECT base_tbl.a, + base_tbl.b + FROM base_tbl + WHERE base_tbl.a < base_tbl.b; +Options: check_option=cascaded + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); + QUERY PLAN +--------------------------- + Insert on public.base_tbl + -> Result + Output: 0, 5 +(3 rows) + +INSERT INTO rw_view VALUES (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); + QUERY PLAN +--------------------------- + Insert on public.base_tbl + -> Result + Output: 0, 15 +(3 rows) + +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM base_tbl; + a | b +----+---- + 10 | 15 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; + QUERY PLAN +------------------------------------------------------------- + Update on public.base_tbl + -> Seq Scan on public.base_tbl + Output: base_tbl.a, (base_tbl.b + 5), base_tbl.ctid + Filter: (base_tbl.a < base_tbl.b) +(4 rows) + +UPDATE rw_view SET b = b + 5; -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (20, 20). +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; + QUERY PLAN +-------------------------------------------------------------- + Update on public.base_tbl + -> Seq Scan on public.base_tbl + Output: base_tbl.a, (base_tbl.b + 15), base_tbl.ctid + Filter: (base_tbl.a < base_tbl.b) +(4 rows) + +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM base_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +--DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl CASCADE; +NOTICE: drop cascades to view rw_view +-- test WCO for partitions +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger + BEFORE INSERT OR UPDATE ON child_tbl + FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +--CREATE FOREIGN TABLE foreign_tbl (a int, b int) +-- SERVER influxdb_svr OPTIONS (table 'child_tbl'); +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION child_tbl FOR VALUES FROM (0) TO (100); +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + View "public.rw_view" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+---------+-----------+----------+---------+---------+------------- + a | integer | | | | plain | + b | integer | | | | plain | +View definition: + SELECT parent_tbl.a, + parent_tbl.b + FROM parent_tbl + WHERE parent_tbl.a < parent_tbl.b; +Options: check_option=cascaded + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); + QUERY PLAN +----------------------------- + Insert on public.parent_tbl + -> Result + Output: 0, 5 +(3 rows) + +INSERT INTO rw_view VALUES (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); + QUERY PLAN +----------------------------- + Insert on public.parent_tbl + -> Result + Output: 0, 15 +(3 rows) + +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM child_tbl; + a | b +----+---- + 10 | 15 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; + QUERY PLAN +---------------------------------------------------------------- + Update on public.parent_tbl + Update on public.child_tbl + -> Seq Scan on public.child_tbl + Output: child_tbl.a, (child_tbl.b + 5), child_tbl.ctid + Filter: (child_tbl.a < child_tbl.b) +(5 rows) + +UPDATE rw_view SET b = b + 5; -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (20, 20). +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; + QUERY PLAN +----------------------------------------------------------------- + Update on public.parent_tbl + Update on public.child_tbl + -> Seq Scan on public.child_tbl + Output: child_tbl.a, (child_tbl.b + 15), child_tbl.ctid + Filter: (child_tbl.a < child_tbl.b) +(5 rows) + +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM child_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +--DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; +NOTICE: drop cascades to view rw_view +DROP FUNCTION row_before_insupd_trigfunc; +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +--create foreign table rem1 (f1 serial, f2 text) +-- server influxdb_svr options(table 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); +ERROR: relation "rem1_f1_seq" does not exist +LINE 1: select pg_catalog.setval('rem1_f1_seq', 10, false); + ^ +insert into loc1(f2) values('hi'); +insert into loc1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into loc1(f2) values('bye remote'); +select * from loc1; + f1 | f2 +----+------------ + 1 | hi + 2 | hi remote + 3 | bye + 4 | bye remote +(4 rows) + +--select * from rem1; +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +--create foreign table grem1 ( +-- a int, +-- b int generated always as (a * 2) stored) +-- server influxdb_svr options(table 'gloc1'); +insert into gloc1 (a) values (1), (2); +update gloc1 set a = 22 where a = 2; +select * from gloc1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +--select * from grem1; +-- =================================================================== +-- test local triggers +-- =================================================================== +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: relation "rem1" does not exist +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: relation "rem1" does not exist +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +delete from loc1; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 +NOTICE: OLD: (2,"hi remote") +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 +NOTICE: OLD: (3,bye) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 +NOTICE: OLD: (4,"bye remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 +NOTICE: OLD: (2,"hi remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 +NOTICE: OLD: (3,bye) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 +NOTICE: OLD: (4,"bye remote") +insert into loc1 values(1,'insert'); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc1 +NOTICE: NEW: (1,insert) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON loc1 +NOTICE: NEW: (1,insert) +update loc1 set f2 = 'update' where f1 = 1; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 +NOTICE: OLD: (1,insert),NEW: (1,update) +update loc1 set f2 = f2 || f2; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_stmt_before ON loc1; +ERROR: trigger "trig_stmt_before" for table "loc1" does not exist +DROP TRIGGER trig_stmt_after ON loc1; +ERROR: trigger "trig_stmt_after" for table "loc1" does not exist +DELETE from loc1; +-- Test WHEN conditions +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON loc1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Insert or update not matching: nothing happens +INSERT INTO loc1 values(1, 'insert'); +UPDATE loc1 set f2 = 'test'; +-- Insert or update matching: triggers are fired +INSERT INTO loc1 values(2, 'update'); +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON loc1 +NOTICE: NEW: (2,update) +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON loc1 +NOTICE: NEW: (2,update) +UPDATE loc1 set f2 = 'update update' where f1 = '2'; +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON loc1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON loc1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM loc1; +NOTICE: trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON loc1 +NOTICE: OLD: (2,"update update") +NOTICE: trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON loc1 +NOTICE: OLD: (2,"update update") +-- cleanup +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_after_insupd ON loc1; +DROP TRIGGER trig_row_before_delete ON loc1; +DROP TRIGGER trig_row_after_delete ON loc1; +-- Test various RETURN statements in BEFORE triggers. +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +-- The new values should have 'triggered' appended +INSERT INTO loc1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! +(1 row) + +INSERT INTO loc1 values(2, 'insert') RETURNING f2; + f2 +-------------------- + insert triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! + 2 | insert triggered ! +(2 rows) + +UPDATE loc1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------- + 1 | triggered ! + 2 | triggered ! +(2 rows) + +UPDATE loc1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------- + skidoo triggered ! + skidoo triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | skidoo triggered ! + 2 | skidoo triggered ! +(2 rows) + +EXPLAIN (verbose, costs off) +UPDATE loc1 set f1 = 10; -- all columns should be transmitted + QUERY PLAN +------------------------------- + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: 10, f2, ctid +(3 rows) + +UPDATE loc1 set f1 = 10; +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 10 | skidoo triggered ! triggered ! + 10 | skidoo triggered ! triggered ! +(2 rows) + +DELETE FROM loc1; +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO loc1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! +(1 row) + +INSERT INTO loc1 values(2, 'insert') RETURNING f2; + f2 +-------------------------------- + insert triggered ! triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! + 2 | insert triggered ! triggered ! +(2 rows) + +UPDATE loc1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------------------- + 1 | triggered ! triggered ! + 2 | triggered ! triggered ! +(2 rows) + +UPDATE loc1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------------------- + skidoo triggered ! triggered ! + skidoo triggered ! triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | skidoo triggered ! triggered ! + 2 | skidoo triggered ! triggered ! +(2 rows) + +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_before_insupd2 ON loc1; +DELETE from loc1; +INSERT INTO loc1 VALUES (1, 'test'); +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); +-- Nothing should have changed. +INSERT INTO loc1 VALUES (2, 'test2'); +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +UPDATE loc1 SET f2 = 'test2'; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DELETE from loc1; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DROP TRIGGER trig_null ON loc1; +DELETE from loc1; +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO loc1(f2) VALUES ('test'); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc1 +NOTICE: NEW: (5,"test triggered !") +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON loc1 +NOTICE: NEW: (5,"test triggered !") +UPDATE loc1 SET f2 = 'testo'; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 +NOTICE: OLD: (5,"test triggered !"),NEW: (5,"testo triggered !") +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 +NOTICE: OLD: (5,"test triggered !"),NEW: (5,"testo triggered !") +-- Test returning a system attribute +INSERT INTO loc1(f2) VALUES ('test') RETURNING ctid; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc1 +NOTICE: NEW: (6,"test triggered !") +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON loc1 +NOTICE: NEW: (6,"test triggered !") + ctid +-------- + (0,29) +(1 row) + +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_local_before ON loc1; +-- Test direct foreign table modification functionality +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_stmt_before ON loc1; +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_stmt_after ON loc1; +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_before_insert ON loc1; +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_after_insert ON loc1; +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_before_update ON loc1; +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_after_update ON loc1; +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can't be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_before_delete ON loc1; +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------ + Update on public.loc1 + -> Seq Scan on public.loc1 + Output: f1, ''::text, ctid +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can't be pushed down + QUERY PLAN +------------------------------- + Delete on public.loc1 + -> Seq Scan on public.loc1 + Output: ctid +(3 rows) + +DROP TRIGGER trig_row_after_delete ON loc1; +-- =================================================================== +-- test inheritance features +-- =================================================================== +CREATE TABLE a (aa TEXT); +--CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +--ALTER TABLE loct SET (autovacuum_enabled = 'false'); +-- Because Influxdb does not support UPDATE, to test locally +-- we create local table. +CREATE TABLE b (bb TEXT) INHERITS (a); +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa +(3 rows) + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE b SET aa = 'new'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | new + b | new + b | new +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-----+---- + b | new | + b | new | + b | new | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE a SET aa = 'newtoo'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo + b | newtoo + b | newtoo + b | newtoo +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+--------+---- + b | newtoo | + b | newtoo | + b | newtoo | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo +(3 rows) + +DELETE FROM a; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+---- +(0 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+----+---- +(0 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+---- +(0 rows) + +DROP TABLE a CASCADE; +NOTICE: drop cascades to table b +--DROP TABLE loct; +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +--create table loct1 (f1 int, f2 int, f3 int); +--create table loct2 (f1 int, f2 int, f3 int); +--alter table loct1 set (autovacuum_enabled = 'false'); +--alter table loct2 set (autovacuum_enabled = 'false'); +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server influxdb_svr options (table 'loct1_1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server influxdb_svr options (table 'loct2_1'); +alter table foo set (autovacuum_enabled = 'false'); +alter table bar set (autovacuum_enabled = 'false'); +insert into foo values(1,1); +insert into foo values(3,3); +-- insert into foo2 values(2,2,2); +-- insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +-- insert into bar2 values(3,33,33); +-- insert into bar2 values(4,44,44); +-- insert into bar2 values(7,77,77); +--explain (verbose, costs off) +--select * from ftbar where f1 in (select f1 from ftfoo) for update; +--select * from ftbar where f1 in (select f1 from ftfoo) for update; +explain (verbose, costs off) +select * from bar2 where f1 in (select f1 from foo2) for update; + QUERY PLAN +---------------------------------------------------------------------------------- + LockRows + Output: bar2.f1, bar2.f2, bar2.f3, bar2.*, foo2.* + -> Hash Join + Output: bar2.f1, bar2.f2, bar2.f3, bar2.*, foo2.* + Inner Unique: true + Hash Cond: (bar2.f1 = foo2.f1) + -> Foreign Scan on public.bar2 + Output: bar2.f1, bar2.f2, bar2.f3, bar2.* + InfluxDB query: SELECT "f1", "f2", "f3" FROM "loct2_1" + -> Hash + Output: foo2.*, foo2.f1 + -> HashAggregate + Output: foo2.*, foo2.f1 + Group Key: foo2.f1 + -> Foreign Scan on public.foo2 + Output: foo2.*, foo2.f1 + InfluxDB query: SELECT "f1", "f2", "f3" FROM "loct1_1" +(17 rows) + +select * from bar2 where f1 in (select f1 from foo2) for update; + f1 | f2 | f3 +----+----+---- + 4 | 44 | 44 +(1 row) + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; + QUERY PLAN +---------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar + Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid + -> Foreign Scan on public.bar2 + Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid + InfluxDB query: SELECT "f1", "f2", "f3" FROM "loct2_1" + -> Hash + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + -> Foreign Scan on public.foo2 + Output: foo2.ctid, foo2.f1, foo2.*, foo2.tableoid + InfluxDB query: SELECT "f1", "f2", "f3" FROM "loct1_1" +(23 rows) + +select * from bar where f1 in (select f1 from foo) for share; + f1 | f2 +----+---- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 +(4 rows) + +-- Check UPDATE with inherited target and an inherited source table +-- skip, if we update to bar, bar2 also is updated because it inherits bar +-- and Influxdb fdw does not support update on bar2. +/* +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + +select tableoid::regclass, * from bar order by 1,2; + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + +select tableoid::regclass, * from bar order by 1,2; + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; +update bar set f2 = null where current of c; +rollback; + +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; +delete from foo where f1 < 5 returning *; +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; +update bar set f2 = f2 + 100 returning *; + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +explain (verbose, costs off) +update bar set f2 = f2 + 100; +update bar set f2 = f2 + 100; + +explain (verbose, costs off) +delete from bar where f2 < 400; +delete from bar where f2 < 400; + +-- cleanup +drop table foo cascade; +drop table bar cascade; +drop table loct1; +drop table loct2; + +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +--create table loct1 (a int, b text); +--create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server influxdb_svr options (table 'loct1'); +create foreign table remt2 (a int, b text) + server influxdb_svr options (table 'loct2'); +alter foreign table remt1 inherit parent; + +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); + +analyze remt1; +analyze remt2; + +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; +delete from parent using remt2 where parent.a = remt2.a returning parent; + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; +*/ -- =================================================================== -- test tuple routing for foreign-table partitions -- =================================================================== +/* -- Test insert tuple routing --- create table itrtest (a int, b text) partition by list (a); --- create foreign table remp1 (a int check (a in (1)), b text) server influxdb_svr options (table 'loct1'); --- create foreign table remp2 (a int check (a in (2)), b text) server influxdb_svr options (table 'loct2'); --- alter table itrtest attach partition remp1 for values in (1); --- alter table itrtest attach partition remp2 for values in (2); --- select tableoid::regclass, * FROM itrtest; --- select tableoid::regclass, * FROM remp1; --- select tableoid::regclass, * FROM remp2; +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); + +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + +select tableoid::regclass, * FROM itrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +delete from itrtest; + +create unique index loct1_idx on loct1 (a); + +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; + +select tableoid::regclass, * FROM itrtest; + +delete from itrtest; + +drop index loct1_idx; + +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); + +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; + +drop table itrtest; +drop table loct1; +drop table loct2; + +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); + +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; + +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; + +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + +drop trigger loct_br_insert_trigger on loct; + +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; +update utrtest set a = 1 returning *; + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); + +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; +update utrtest set a = 3 returning *; -- ERROR + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR + +drop table utrtest; +drop table loct; + +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); + +copy ctrtest from stdin; +1 foo +2 qux +\. + +select tableoid::regclass, * FROM ctrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +1 bar +\. + +select tableoid::regclass, * FROM remp1; + +drop table ctrtest; +drop table loct1; +drop table loct2; +*/ +-- =================================================================== +-- test COPY FROM +-- =================================================================== +/* +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; + +delete from rem2; + +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on rem2; + +delete from rem2; + +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on rem2; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; + +delete from rem2; + +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +1 foo +2 bar +\. +commit; +select * from rem3; +drop foreign table rem3; +drop table loc3; +*/ -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== @@ -4910,11 +6799,13 @@ IMPORT FOREIGN SCHEMA public FROM SERVER influxdb_svr INTO import_influx1; import_influx1 | fprt2_p1 | influxdb_svr | ("table" 'fprt2_p1') | import_influx1 | fprt2_p2 | influxdb_svr | ("table" 'fprt2_p2') | import_influx1 | loct1 | influxdb_svr | ("table" 'loct1') | + import_influx1 | loct1_1 | influxdb_svr | ("table" 'loct1_1') | import_influx1 | loct2 | influxdb_svr | ("table" 'loct2') | + import_influx1 | loct2_1 | influxdb_svr | ("table" 'loct2_1') | import_influx1 | pagg_tab_p1 | influxdb_svr | ("table" 'pagg_tab_p1') | import_influx1 | pagg_tab_p2 | influxdb_svr | ("table" 'pagg_tab_p2') | import_influx1 | pagg_tab_p3 | influxdb_svr | ("table" 'pagg_tab_p3') | -(14 rows) +(16 rows) \d import_influx1.* Foreign table "import_influx1.T1" @@ -5018,6 +6909,16 @@ FDW options: ("table" 'fprt2_p2') Server: influxdb_svr FDW options: ("table" 'loct1') + Foreign table "import_influx1.loct1_1" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+------------- + time | timestamp with time zone | | | | + f1 | bigint | | | | + f2 | bigint | | | | + f3 | bigint | | | | +Server: influxdb_svr +FDW options: ("table" 'loct1_1') + Foreign table "import_influx1.loct2" Column | Type | Collation | Nullable | Default | FDW options --------+--------------------------+-----------+----------+---------+------------- @@ -5027,6 +6928,16 @@ FDW options: ("table" 'loct1') Server: influxdb_svr FDW options: ("table" 'loct2') + Foreign table "import_influx1.loct2_1" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+------------- + time | timestamp with time zone | | | | + f1 | bigint | | | | + f2 | bigint | | | | + f3 | bigint | | | | +Server: influxdb_svr +FDW options: ("table" 'loct2_1') + Foreign table "import_influx1.pagg_tab_p1" Column | Type | Collation | Nullable | Default | FDW options --------+--------------------------+-----------+----------+---------+------------- @@ -5084,11 +6995,13 @@ IMPORT FOREIGN SCHEMA public EXCEPT ("T1", loct, nonesuch) import_influx2 | fprt2_p1 | influxdb_svr | ("table" 'fprt2_p1') | import_influx2 | fprt2_p2 | influxdb_svr | ("table" 'fprt2_p2') | import_influx2 | loct1 | influxdb_svr | ("table" 'loct1') | + import_influx2 | loct1_1 | influxdb_svr | ("table" 'loct1_1') | import_influx2 | loct2 | influxdb_svr | ("table" 'loct2') | + import_influx2 | loct2_1 | influxdb_svr | ("table" 'loct2_1') | import_influx2 | pagg_tab_p1 | influxdb_svr | ("table" 'pagg_tab_p1') | import_influx2 | pagg_tab_p2 | influxdb_svr | ("table" 'pagg_tab_p2') | import_influx2 | pagg_tab_p3 | influxdb_svr | ("table" 'pagg_tab_p3') | -(14 rows) +(16 rows) -- Assorted error cases IMPORT FOREIGN SCHEMA public FROM SERVER influxdb_svr INTO import_influx2; @@ -5101,6 +7014,46 @@ IMPORT FOREIGN SCHEMA nonesuch FROM SERVER influxdb_svr INTO notthere; ERROR: schema "notthere" does not exist IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; ERROR: server "nowhere" does not exist +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +-- CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +--IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) +-- FROM SERVER influxdb_svr INTO import_dest5; -- ERROR +ROLLBACK; +-- BEGIN; +-- CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=101']; +-- ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=101']; +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=202']; +-- CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=30000']; +-- ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=30000']; +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=60000']; +-- ROLLBACK; -- =================================================================== -- test partitionwise joins -- =================================================================== @@ -5118,6 +7071,9 @@ CREATE FOREIGN TABLE ftprt2_p1 (a int, b int, c text) ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER influxdb_svr OPTIONS (table 'fprt2_p2'); +-- ANALYZE fprt2; +-- ANALYZE fprt2_p1; +-- ANALYZE fprt2_p2; -- inner join three tables EXPLAIN (COSTS OFF) SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; @@ -5155,23 +7111,30 @@ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER J 400 | 400 | 0008 (4 rows) --- left outer join + nullable clasue -EXPLAIN (COSTS OFF) +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; - QUERY PLAN ---------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------- Sort + Output: t1.a, ftprt2_p1.b, ftprt2_p1.c Sort Key: t1.a, ftprt2_p1.b, ftprt2_p1.c - -> Append - -> Merge Left Join - Merge Cond: ((t1.a = ftprt2_p1.b) AND (t1.b = ftprt2_p1.a)) - -> Sort - Sort Key: t1.a, t1.b - -> Foreign Scan on ftprt1_p1 t1 - -> Sort - Sort Key: ftprt2_p1.b, ftprt2_p1.a - -> Foreign Scan on ftprt2_p1 -(11 rows) + -> Merge Left Join + Output: t1.a, ftprt2_p1.b, ftprt2_p1.c + Merge Cond: ((t1.a = ftprt2_p1.b) AND (t1.b = ftprt2_p1.a)) + -> Sort + Output: t1.a, t1.b + Sort Key: t1.a, t1.b + -> Foreign Scan on public.ftprt1_p1 t1 + Output: t1.a, t1.b + InfluxDB query: SELECT "a", "b" FROM "fprt1_p1" WHERE (("a" < 10)) + -> Sort + Output: ftprt2_p1.b, ftprt2_p1.c, ftprt2_p1.a + Sort Key: ftprt2_p1.b, ftprt2_p1.a + -> Foreign Scan on public.ftprt2_p1 + Output: ftprt2_p1.b, ftprt2_p1.c, ftprt2_p1.a + InfluxDB query: SELECT "a", "b", "c" FROM "fprt2_p1" WHERE (("a" < 10)) +(18 rows) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; a | b | c @@ -5325,6 +7288,10 @@ CREATE TABLE pagg_tab (t int, a int, b int, c text) PARTITION BY RANGE(a); CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER influxdb_svr OPTIONS (table 'pagg_tab_p1'); CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER influxdb_svr OPTIONS (table 'pagg_tab_p2');; CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER influxdb_svr2 OPTIONS (table 'pagg_tab_p3');; +-- ANALYZE pagg_tab; +-- ANALYZE fpagg_tab_p1; +-- ANALYZE fpagg_tab_p2; +-- ANALYZE fpagg_tab_p3; -- When GROUP BY clause matches with PARTITION KEY. -- Plan with partitionwise aggregates is disabled SET enable_partitionwise_aggregate TO false; @@ -5449,7 +7416,7 @@ DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr2; DROP SERVER testserver1 CASCADE; DROP SERVER influxdb_svr CASCADE; -NOTICE: drop cascades to 53 other objects +NOTICE: drop cascades to 61 other objects DETAIL: drop cascades to foreign table "S 1"."T1" drop cascades to foreign table "S 1"."T2" drop cascades to foreign table "S 1"."T3" @@ -5460,7 +7427,9 @@ drop cascades to foreign table "S 1".fprt1_p2 drop cascades to foreign table "S 1".fprt2_p1 drop cascades to foreign table "S 1".fprt2_p2 drop cascades to foreign table "S 1".loct1 +drop cascades to foreign table "S 1".loct1_1 drop cascades to foreign table "S 1".loct2 +drop cascades to foreign table "S 1".loct2_1 drop cascades to foreign table "S 1".pagg_tab_p1 drop cascades to foreign table "S 1".pagg_tab_p2 drop cascades to foreign table "S 1".pagg_tab_p3 @@ -5469,6 +7438,8 @@ drop cascades to foreign table ft2 drop cascades to foreign table ft4 drop cascades to foreign table ft5 drop cascades to foreign table ft3 +drop cascades to foreign table foo2 +drop cascades to foreign table bar2 drop cascades to foreign table import_influx1."T1" drop cascades to foreign table import_influx1."T2" drop cascades to foreign table import_influx1."T3" @@ -5479,7 +7450,9 @@ drop cascades to foreign table import_influx1.fprt1_p2 drop cascades to foreign table import_influx1.fprt2_p1 drop cascades to foreign table import_influx1.fprt2_p2 drop cascades to foreign table import_influx1.loct1 +drop cascades to foreign table import_influx1.loct1_1 drop cascades to foreign table import_influx1.loct2 +drop cascades to foreign table import_influx1.loct2_1 drop cascades to foreign table import_influx1.pagg_tab_p1 drop cascades to foreign table import_influx1.pagg_tab_p2 drop cascades to foreign table import_influx1.pagg_tab_p3 @@ -5493,7 +7466,9 @@ drop cascades to foreign table import_influx2.fprt1_p2 drop cascades to foreign table import_influx2.fprt2_p1 drop cascades to foreign table import_influx2.fprt2_p2 drop cascades to foreign table import_influx2.loct1 +drop cascades to foreign table import_influx2.loct1_1 drop cascades to foreign table import_influx2.loct2 +drop cascades to foreign table import_influx2.loct2_1 drop cascades to foreign table import_influx2.pagg_tab_p1 drop cascades to foreign table import_influx2.pagg_tab_p2 drop cascades to foreign table import_influx2.pagg_tab_p3 diff --git a/expected/extra/join.out b/expected/extra/join.out index e509e94..0bc800e 100644 --- a/expected/extra/join.out +++ b/expected/extra/join.out @@ -35,7 +35,8 @@ CREATE FOREIGN TABLE tenk1 ( stringu2 name, string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); -ALTER TABLE tenk1 SET WITH OIDS; +--Does not support on Postgres 12 +--ALTER TABLE tenk1 SET WITH OIDS; CREATE FOREIGN TABLE tenk2 ( unique1 int4, unique2 int4, @@ -61,6 +62,10 @@ CREATE FOREIGN TABLE INT8_TBL( q2 int8 ) SERVER influxdb_svr; CREATE FOREIGN TABLE INT2_TBL(f1 int2) SERVER influxdb_svr; +-- useful in some tests below +create temp table onerow(); +insert into onerow default values; +analyze onerow; -- -- CORRELATION NAMES -- Make sure that table/column aliases are supported @@ -135,97 +140,97 @@ SELECT '' AS "xxx", * (11 rows) SELECT '' AS "xxx", * - FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) ORDER BY a; + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e); xxx | a | b | c | d | e -----+---+---+-------+---+---- + | 1 | 4 | one | 1 | -1 + | 2 | 3 | two | 1 | -1 + | 3 | 2 | three | 1 | -1 + | 4 | 1 | four | 1 | -1 + | 5 | 0 | five | 1 | -1 + | 6 | 6 | six | 1 | -1 + | 7 | 7 | seven | 1 | -1 + | 8 | 8 | eight | 1 | -1 | 0 | | zero | 1 | -1 - | 0 | | zero | | 0 - | 0 | | zero | 0 | - | 0 | | zero | 5 | -5 - | 0 | | zero | 5 | -5 - | 0 | | zero | 2 | 4 - | 0 | | zero | 3 | -3 - | 0 | | zero | 2 | 2 - | 1 | 4 | one | 3 | -3 + | | | null | 1 | -1 + | | 0 | zero | 1 | -1 | 1 | 4 | one | 2 | 2 - | 1 | 4 | one | 1 | -1 - | 1 | 4 | one | 2 | 4 - | 1 | 4 | one | 5 | -5 - | 1 | 4 | one | 5 | -5 - | 1 | 4 | one | 0 | - | 1 | 4 | one | | 0 | 2 | 3 | two | 2 | 2 - | 2 | 3 | two | 0 | - | 2 | 3 | two | | 0 - | 2 | 3 | two | 5 | -5 - | 2 | 3 | two | 1 | -1 - | 2 | 3 | two | 3 | -3 - | 2 | 3 | two | 5 | -5 - | 2 | 3 | two | 2 | 4 - | 3 | 2 | three | 2 | 4 | 3 | 2 | three | 2 | 2 - | 3 | 2 | three | | 0 - | 3 | 2 | three | 1 | -1 - | 3 | 2 | three | 0 | + | 4 | 1 | four | 2 | 2 + | 5 | 0 | five | 2 | 2 + | 6 | 6 | six | 2 | 2 + | 7 | 7 | seven | 2 | 2 + | 8 | 8 | eight | 2 | 2 + | 0 | | zero | 2 | 2 + | | | null | 2 | 2 + | | 0 | zero | 2 | 2 + | 1 | 4 | one | 3 | -3 + | 2 | 3 | two | 3 | -3 | 3 | 2 | three | 3 | -3 - | 3 | 2 | three | 5 | -5 - | 3 | 2 | three | 5 | -5 | 4 | 1 | four | 3 | -3 + | 5 | 0 | five | 3 | -3 + | 6 | 6 | six | 3 | -3 + | 7 | 7 | seven | 3 | -3 + | 8 | 8 | eight | 3 | -3 + | 0 | | zero | 3 | -3 + | | | null | 3 | -3 + | | 0 | zero | 3 | -3 + | 1 | 4 | one | 2 | 4 + | 2 | 3 | two | 2 | 4 + | 3 | 2 | three | 2 | 4 | 4 | 1 | four | 2 | 4 - | 4 | 1 | four | 5 | -5 - | 4 | 1 | four | 5 | -5 - | 4 | 1 | four | 1 | -1 - | 4 | 1 | four | 0 | - | 4 | 1 | four | | 0 - | 4 | 1 | four | 2 | 2 | 5 | 0 | five | 2 | 4 - | 5 | 0 | five | 3 | -3 - | 5 | 0 | five | | 0 - | 5 | 0 | five | 5 | -5 - | 5 | 0 | five | 2 | 2 - | 5 | 0 | five | 0 | - | 5 | 0 | five | 5 | -5 - | 5 | 0 | five | 1 | -1 - | 6 | 6 | six | 5 | -5 - | 6 | 6 | six | 5 | -5 | 6 | 6 | six | 2 | 4 - | 6 | 6 | six | | 0 - | 6 | 6 | six | 3 | -3 - | 6 | 6 | six | 1 | -1 - | 6 | 6 | six | 0 | - | 6 | 6 | six | 2 | 2 | 7 | 7 | seven | 2 | 4 - | 7 | 7 | seven | 1 | -1 - | 7 | 7 | seven | 5 | -5 - | 7 | 7 | seven | 3 | -3 - | 7 | 7 | seven | 5 | -5 - | 7 | 7 | seven | 2 | 2 - | 7 | 7 | seven | 0 | - | 7 | 7 | seven | | 0 - | 8 | 8 | eight | | 0 | 8 | 8 | eight | 2 | 4 - | 8 | 8 | eight | 0 | - | 8 | 8 | eight | 3 | -3 + | 0 | | zero | 2 | 4 + | | | null | 2 | 4 + | | 0 | zero | 2 | 4 + | 1 | 4 | one | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 8 | 8 | eight | 5 | -5 - | 8 | 8 | eight | 1 | -1 + | 0 | | zero | 5 | -5 + | | | null | 5 | -5 + | | 0 | zero | 5 | -5 + | 1 | 4 | one | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 8 | 8 | eight | 5 | -5 - | 8 | 8 | eight | 2 | 2 - | | | null | 0 | + | 0 | | zero | 5 | -5 | | | null | 5 | -5 - | | 0 | zero | | 0 - | | | null | 2 | 2 - | | 0 | zero | 3 | -3 - | | | null | 3 | -3 | | 0 | zero | 5 | -5 - | | | null | 1 | -1 + | 1 | 4 | one | 0 | + | 2 | 3 | two | 0 | + | 3 | 2 | three | 0 | + | 4 | 1 | four | 0 | + | 5 | 0 | five | 0 | + | 6 | 6 | six | 0 | + | 7 | 7 | seven | 0 | + | 8 | 8 | eight | 0 | + | 0 | | zero | 0 | + | | | null | 0 | | | 0 | zero | 0 | - | | 0 | zero | 5 | -5 + | 1 | 4 | one | | 0 + | 2 | 3 | two | | 0 + | 3 | 2 | three | | 0 + | 4 | 1 | four | | 0 + | 5 | 0 | five | | 0 + | 6 | 6 | six | | 0 + | 7 | 7 | seven | | 0 + | 8 | 8 | eight | | 0 + | 0 | | zero | | 0 | | | null | | 0 - | | | null | 5 | -5 - | | 0 | zero | 2 | 4 - | | 0 | zero | 1 | -1 - | | | null | 2 | 4 - | | 0 | zero | 2 | 2 + | | 0 | zero | | 0 (88 rows) SELECT '' AS "xxx", t1.a, t2.e @@ -1761,6 +1766,20 @@ NATURAL FULL JOIN ee | | 42 | 2 | (4 rows) +-- Constants as join keys can also be problematic +SELECT * FROM + (SELECT name, n as s1_n FROM t11) as s1 +FULL JOIN + (SELECT name, 2 as s2_n FROM t21) as s2 +ON (s1_n = s2_n); + name | s1_n | name | s2_n +------+------+------+------ + | | bb | 2 + | | cc | 2 + | | ee | 2 + bb | 11 | | +(4 rows) + -- Test for propagation of nullability constraints into sub-joins create foreign table x (x1 int, x2 int) server influxdb_svr; create foreign table y (y1 int, y2 int) server influxdb_svr; @@ -1931,20 +1950,16 @@ explain (costs off) select * from int8_tbl i1 left join (int8_tbl i2 join (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 order by 1, 2; - QUERY PLAN ------------------------------------------------------ + QUERY PLAN +----------------------------------------------- Sort Sort Key: i1.q1, i1.q2 -> Hash Left Join Hash Cond: (i1.q2 = i2.q2) -> Foreign Scan on int8_tbl i1 -> Hash - -> Hash Join - Hash Cond: (i2.q1 = (123)) - -> Foreign Scan on int8_tbl i2 - -> Hash - -> Result -(11 rows) + -> Foreign Scan on int8_tbl i2 +(7 rows) select * from int8_tbl i1 left join (int8_tbl i2 join (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 @@ -2101,6 +2116,22 @@ DROP FOREIGN TABLE J2_TBL; CREATE FOREIGN TABLE t12 (a int, b int) SERVER influxdb_svr; CREATE FOREIGN TABLE t22 (a int, b int) SERVER influxdb_svr; CREATE FOREIGN TABLE t32 (x int, y int) SERVER influxdb_svr; +-- InfluxDB FDW does not support INSERT/DELETE/UPDATE +--INSERT INTO t1 VALUES (5, 10); +--INSERT INTO t1 VALUES (15, 20); +--INSERT INTO t1 VALUES (100, 100); +--INSERT INTO t1 VALUES (200, 1000); +--INSERT INTO t2 VALUES (200, 2000); +--INSERT INTO t3 VALUES (5, 20); +--INSERT INTO t3 VALUES (6, 7); +--INSERT INTO t3 VALUES (7, 8); +--INSERT INTO t3 VALUES (500, 100); +--DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a; +--SELECT * FROM t3; +--DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a; +--SELECT * FROM t3; +--DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y; +--SELECT * FROM t3; -- Test join against inheritance tree create temp table t2a () inherits (t22); insert into t2a values (200, 2001); @@ -2471,7 +2502,7 @@ ON sub1.key1 = sub2.key3; 1 | 1 | 1 | 1 (1 row) -test the path using join aliases, too +-- test the path using join aliases, too SELECT * FROM ( SELECT 1 as key1 ) sub1 LEFT JOIN @@ -2487,9 +2518,11 @@ LEFT JOIN ON sub4.key5 = sub3.key3 ) sub2 ON sub1.key1 = sub2.key3; -ERROR: syntax error at or near "test" -LINE 1: test the path using join aliases, too - ^ + key1 | key3 | value2 | value3 +------+------+--------+-------- + 1 | 1 | 1 | 1 +(1 row) + -- -- test case where a PlaceHolderVar is used as a nestloop parameter -- @@ -2762,8 +2795,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -2773,27 +2806,24 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; QUERY PLAN --------------------------------------------------------------------------- Nested Loop - Join Filter: ((0) = i1.f1) -> Nested Loop Join Filter: ((t1.stringu1 > t2.stringu2) AND ((3) = t2.unique1)) - -> Hash Join - Hash Cond: (t1.unique2 = (11)) + -> Nested Loop + Join Filter: ((11) = t1.unique2) + -> Nested Loop + -> Seq Scan on onerow + -> Seq Scan on onerow onerow_1 -> Foreign Scan on tenk1 t1 - -> Hash - -> Nested Loop - Join Filter: ((1) = (1)) - -> Result - -> Result -> Foreign Scan on tenk1 t2 -> Foreign Scan on int4_tbl i1 -(14 rows) +(11 rows) select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -2825,6 +2855,51 @@ where t1.unique1 < i4.f1; ---- (0 rows) +-- this variant is foldable by the remove-useless-RESULT-RTEs code +explain (costs off) +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + QUERY PLAN +----------------------------------------------------- + Hash Join + Hash Cond: (t2.unique1 = (3)) + Join Filter: (t1.stringu1 > t2.stringu2) + -> Foreign Scan on tenk1 t2 + -> Hash + -> Hash Join + Hash Cond: (t1.unique2 = (11)) + -> Foreign Scan on tenk1 t1 + -> Hash + -> Foreign Scan on int4_tbl i1 +(10 rows) + +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + unique2 | stringu1 | unique1 | stringu2 +---------+----------+---------+---------- + 11 | WFAAAA | 3 | LKIAAA +(1 row) + -- -- test extraction of restriction OR clauses from join OR clause -- (we used to only do this for indexable clauses) @@ -3262,7 +3337,7 @@ select t1.* from -> Sort Output: i8.q2, (NULL::integer) Sort Key: (NULL::integer) - -> Merge Left Join + -> Merge Join Output: i8.q2, (NULL::integer) Merge Cond: (i8.q1 = i8b2.q1) -> Sort @@ -3350,7 +3425,6 @@ select t1.* from -> Nested Loop Output: i8b2.q1, NULL::integer -> Foreign Scan on public.int4_tbl i4b2 - Output: i4b2.f1 InfluxDB query: SELECT * FROM "int4_tbl" -> Materialize Output: i8b2.q1 @@ -3362,7 +3436,7 @@ select t1.* from -> Foreign Scan on public.int4_tbl i4 Output: i4.f1 InfluxDB query: SELECT "f1" FROM "int4_tbl" -(54 rows) +(53 rows) select t1.* from text_tbl t1 @@ -3636,7 +3710,6 @@ where tt1.f1 = ss1.c0; -> Nested Loop Output: tt1.f1 -> Foreign Scan on public.text_tbl tt2 - Output: tt2.f1 InfluxDB query: SELECT * FROM "text_tbl" -> Materialize Output: tt1.f1 @@ -3662,7 +3735,7 @@ where tt1.f1 = ss1.c0; -> Foreign Scan on public.text_tbl tt5 Output: tt4.f1 InfluxDB query: SELECT * FROM "text_tbl" -(33 rows) +(32 rows) select 1 from text_tbl as tt1 @@ -3753,10 +3826,10 @@ select * from QUERY PLAN --------------------------------------- Nested Loop Left Join - Join Filter: ((1) = COALESCE((1))) -> Result -> Hash Full Join Hash Cond: (a1.unique1 = (1)) + Filter: (1 = COALESCE((1))) -> Foreign Scan on tenk1 a1 -> Hash -> Result @@ -4192,12 +4265,12 @@ where ss.stringu2 !~* ss.case1; -> Sort Sort Key: t1.unique2 -> Merge Right Join - Merge Cond: (u1.f1 = ((t1.string4)::text)) + Merge Cond: (u1.f1 = t1.string4) -> Sort - Sort Key: u1.f1 + Sort Key: u1.f1 COLLATE "C" -> Foreign Scan on uniquetbl u1 -> Sort - Sort Key: ((t1.string4)::text) + Sort Key: t1.string4 -> Merge Join Merge Cond: ((CASE t1.ten WHEN 0 THEN 'doh!'::text ELSE NULL::text END) = t0.f1) -> Sort @@ -4268,11 +4341,11 @@ HINT: Perhaps you meant to reference the column "t1.unique1" or the column "t2. -- -- Take care to reference the correct RTE -- --- select atts.relid::regclass, s.* from pg_stats s join --- pg_attribute a on s.attname = a.attname and s.tablename = --- a.attrelid::regclass::text join (select unnest(indkey) attnum, --- indexrelid from pg_index i) atts on atts.attnum = a.attnum where --- schemaname != 'pg_catalog'; +--select atts.relid::regclass, s.* from pg_stats s join +-- pg_attribute a on s.attname = a.attname and s.tablename = +-- a.attrelid::regclass::text join (select unnest(indkey) attnum, +-- indexrelid from pg_index i) atts on atts.attnum = a.attnum where +-- schemaname != 'pg_catalog'; -- -- Test LATERAL -- @@ -4781,7 +4854,7 @@ analyze dual; select v.* from (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy); + lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); vx | vy -------------------+------------------- 4567890123456789 | @@ -4909,35 +4982,28 @@ select * from int4_tbl i left join explain (verbose, costs off) select * from int4_tbl i left join - lateral (select coalesce(j) from int2_tbl j where i.f1 = j.f1) k on true; - QUERY PLAN ------------------------------------------------------------ - Merge Right Join - Output: i.f1, (COALESCE(j.*)) - Merge Cond: (j.f1 = i.f1) - -> Sort - Output: j.f1, (COALESCE(j.*)) - Sort Key: j.f1 - -> Foreign Scan on public.int2_tbl j - Output: j.f1, COALESCE(j.*) - InfluxDB query: SELECT "f1" FROM "int2_tbl" - -> Sort - Output: i.f1 - Sort Key: i.f1 - -> Foreign Scan on public.int4_tbl i - Output: i.f1 - InfluxDB query: SELECT "f1" FROM "int4_tbl" -(15 rows) + lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; + QUERY PLAN +------------------------------------------------------------------------- + Nested Loop Left Join + Output: i.f1, (COALESCE(i.*)) + -> Foreign Scan on public.int4_tbl i + Output: i.f1, i.* + InfluxDB query: SELECT "f1" FROM "int4_tbl" + -> Foreign Scan on public.int2_tbl j + Output: j.f1, COALESCE(i.*) + InfluxDB query: SELECT "f1" FROM "int2_tbl" WHERE (($1 = "f1")) +(8 rows) select * from int4_tbl i left join - lateral (select coalesce(j) from int2_tbl j where i.f1 = j.f1) k on true; + lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; f1 | coalesce -------------+---------- - -2147483647 | - -123456 | 0 | (0) 123456 | + -123456 | 2147483647 | + -2147483647 | (5 rows) explain (verbose, costs off) @@ -5015,7 +5081,7 @@ select * from -> Nested Loop Output: b.q1, c.q1, LEAST(a.q1, b.q1, c.q1) -> Foreign Scan on public.int8_tbl b - Output: b.q1, b.q2 + Output: b.q1 InfluxDB query: SELECT "q1" FROM "int8_tbl" WHERE (($1 = "q1")) -> Materialize Output: c.q1 @@ -5205,7 +5271,38 @@ select * from Output: 3 (11 rows) -check handling of nested appendrels inside LATERAL +-- check dummy rels with lateral references (bug #15694) +explain (verbose, costs off) +select * from int8_tbl i8 left join lateral + (select *, i8.q2 from int4_tbl where false) ss on true; + QUERY PLAN +----------------------------------------------------------- + Nested Loop Left Join + Output: i8.q1, i8.q2, f1, (i8.q2) + -> Foreign Scan on public.int8_tbl i8 + Output: i8.q1, i8.q2 + InfluxDB query: SELECT "q1", "q2" FROM "int8_tbl" + -> Result + Output: f1, i8.q2 + One-Time Filter: false +(8 rows) + +explain (verbose, costs off) +select * from int8_tbl i8 left join lateral + (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true; + QUERY PLAN +----------------------------------------------------------- + Nested Loop Left Join + Output: i8.q1, i8.q2, f1, f1, (i8.q2) + -> Foreign Scan on public.int8_tbl i8 + Output: i8.q1, i8.q2 + InfluxDB query: SELECT "q1", "q2" FROM "int8_tbl" + -> Result + Output: f1, f1, i8.q2 + One-Time Filter: false +(8 rows) + +-- check handling of nested appendrels inside LATERAL select * from ((select 2 as v) union all (select 3 as v)) as q1 cross join lateral @@ -5214,9 +5311,16 @@ select * from union all (select q1.v) ) as q2; -ERROR: syntax error at or near "check" -LINE 1: check handling of nested appendrels inside LATERAL - ^ + v | v +---+--- + 2 | 4 + 2 | 5 + 2 | 2 + 3 | 4 + 3 | 5 + 3 | 3 +(6 rows) + -- check we don't try to do a unique-ified semijoin with LATERAL explain (verbose, costs off) select * from @@ -5293,7 +5397,7 @@ lateral (select * from int8_tbl t1, -> Result Output: ($3 = 0) -> Foreign Scan on public.int8_tbl t3 - Output: t3.q1, t3.q2 + Output: t3.q2 InfluxDB query: SELECT "q2" FROM "int8_tbl" WHERE (("q2" = $1)) (29 rows) @@ -5834,19 +5938,20 @@ CREATE FOREIGN TABLE onek ( stringu2 name, string4 name ) SERVER influxdb_svr; +-- check that semijoin inner is not seen as unique for a portion of the outerrel explain (verbose, costs off) select t1.unique1, t2.hundred from onek t1, tenk1 t2 where exists (select 1 from tenk1 t3 where t3.thousand = t1.unique1 and t3.tenthous = t2.hundred) and t1.unique1 < 1; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Hash Join Output: t1.unique1, t2.hundred Hash Cond: (t2.hundred = t3.tenthous) -> Foreign Scan on public.tenk1 t2 - Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 + Output: t2.hundred InfluxDB query: SELECT "hundred" FROM "tenk" -> Hash Output: t1.unique1, t3.tenthous @@ -5854,7 +5959,7 @@ where exists (select 1 from tenk1 t3 Output: t1.unique1, t3.tenthous Hash Cond: (t1.unique1 = t3.thousand) -> Foreign Scan on public.onek t1 - Output: t1.unique1, t1.unique2, t1.two, t1.four, t1.ten, t1.twenty, t1.hundred, t1.thousand, t1.twothousand, t1.fivethous, t1.tenthous, t1.odd, t1.even, t1.stringu1, t1.stringu2, t1.string4 + Output: t1.unique1 InfluxDB query: SELECT "unique1" FROM "onek" WHERE (("unique1" < 1)) -> Hash Output: t3.thousand, t3.tenthous @@ -5876,13 +5981,13 @@ from onek t1, tenk1 t2 where exists (select 1 from j3 where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred) and t1.unique1 < 1; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------ Hash Join Output: t1.unique1, t2.hundred Hash Cond: (t2.hundred = j3.tenthous) -> Foreign Scan on public.tenk1 t2 - Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 + Output: t2.hundred InfluxDB query: SELECT "hundred" FROM "tenk" -> Hash Output: t1.unique1, j3.tenthous @@ -5890,7 +5995,7 @@ where exists (select 1 from j3 Output: t1.unique1, j3.tenthous Hash Cond: (t1.unique1 = j3.unique1) -> Foreign Scan on public.onek t1 - Output: t1.unique1, t1.unique2, t1.two, t1.four, t1.ten, t1.twenty, t1.hundred, t1.thousand, t1.twothousand, t1.fivethous, t1.tenthous, t1.odd, t1.even, t1.stringu1, t1.stringu2, t1.string4 + Output: t1.unique1 InfluxDB query: SELECT "unique1" FROM "onek" WHERE (("unique1" < 1)) -> Hash Output: j3.unique1, j3.tenthous diff --git a/expected/extra/limit.out b/expected/extra/limit.out index 7b1de79..4618490 100644 --- a/expected/extra/limit.out +++ b/expected/extra/limit.out @@ -9,41 +9,41 @@ CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr OPTIONS (user 'user', p -- Check the LIMIT/OFFSET feature of SELECT -- CREATE FOREIGN TABLE onek ( - unique1 int4, - unique2 int4, - two int4, - four int4, - ten int4, - twenty int4, - hundred int4, - thousand int4, - twothousand int4, - fivethous int4, - tenthous int4, - odd int4, - even int4, - stringu1 name, - stringu2 name, - string4 name + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name ) SERVER influxdb_svr; CREATE FOREIGN TABLE int8_tbl(q1 int8, q2 int8) SERVER influxdb_svr; CREATE FOREIGN TABLE tenk1 ( - unique1 int4, - unique2 int4, - two int4, - four int4, - ten int4, - twenty int4, - hundred int4, - thousand int4, - twothousand int4, - fivethous int4, - tenthous int4, - odd int4, - even int4, - stringu1 name, - stringu2 name, - string4 name + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); SELECT ''::text AS two, unique1, unique2, stringu1 FROM onek WHERE unique1 > 50 @@ -175,7 +175,7 @@ select * from int8_tbl offset (case when random() < 0.5 then null::bigint end); -- Test assorted cases involving backwards fetch from a LIMIT plan node begin; -declare c1 cursor for select * from int8_tbl limit 10; +declare c1 scroll cursor for select * from int8_tbl limit 10; fetch all in c1; q1 | q2 ------------------+------------------- @@ -192,56 +192,143 @@ fetch 1 in c1; (0 rows) fetch backward 1 in c1; -ERROR: cursor can only scan forward -HINT: Declare it with SCROLL option to enable backward scan. + q1 | q2 +------------------+------------------- + 4567890123456789 | -4567890123456789 +(1 row) + fetch backward all in c1; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------ + 4567890123456789 | 4567890123456789 + 4567890123456789 | 123 + 123 | 4567890123456789 + 123 | 456 +(4 rows) + fetch backward 1 in c1; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch all in c1; -ERROR: current transaction is aborted, commands ignored until end of transaction block -declare c2 cursor for select * from int8_tbl limit 3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +declare c2 scroll cursor for select * from int8_tbl limit 3; fetch all in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------ + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 +(3 rows) + fetch 1 in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch backward 1 in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+----- + 4567890123456789 | 123 +(1 row) + fetch backward all in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +-----+------------------ + 123 | 4567890123456789 + 123 | 456 +(2 rows) + fetch backward 1 in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch all in c2; -ERROR: current transaction is aborted, commands ignored until end of transaction block -declare c3 cursor for select * from int8_tbl offset 3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------ + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 +(3 rows) + +declare c3 scroll cursor for select * from int8_tbl offset 3; fetch all in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------- + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(2 rows) + fetch 1 in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch backward 1 in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------- + 4567890123456789 | -4567890123456789 +(1 row) + fetch backward all in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------ + 4567890123456789 | 4567890123456789 +(1 row) + fetch backward 1 in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch all in c3; -ERROR: current transaction is aborted, commands ignored until end of transaction block -declare c4 cursor for select * from int8_tbl offset 10; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +------------------+------------------- + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(2 rows) + +declare c4 scroll cursor for select * from int8_tbl offset 10; fetch all in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch 1 in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch backward 1 in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch backward all in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch backward 1 in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + fetch all in c4; -ERROR: current transaction is aborted, commands ignored until end of transaction block + q1 | q2 +----+---- +(0 rows) + rollback; -- Stress test for variable LIMIT in conjunction with bounded-heap sorting SELECT @@ -448,26 +535,12 @@ order by s2 desc; (3 rows) -- test for failure to set all aggregates' aggtranstype -explain (verbose, costs off) -select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 - from tenk1 group by thousand order by thousand limit 3; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------ - Limit - Output: (sum(tenthous)), ((((sum(tenthous)))::double precision + (random() * '0'::double precision))), thousand - -> Result - Output: (sum(tenthous)), (((sum(tenthous)))::double precision + (random() * '0'::double precision)), thousand - -> Sort - Output: (sum(tenthous)), thousand - Sort Key: tenk1.thousand - -> Foreign Scan - Output: (sum(tenthous)), thousand - InfluxDB query: SELECT sum("tenthous") FROM "tenk" GROUP BY "thousand" -(10 rows) - -select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 - from tenk1 group by thousand order by thousand limit 3; -ERROR: invalid input syntax for integer: "" +-- influxdb does not support group by clause. +-- explain (verbose, costs off) +-- select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 +-- from tenk1 group by thousand order by thousand limit 3; +-- select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 +-- from tenk1 group by thousand order by thousand limit 3; -- Clean up DO $d$ declare diff --git a/expected/extra/prepare.out b/expected/extra/prepare.out index bf16fe2..8988c6f 100644 --- a/expected/extra/prepare.out +++ b/expected/extra/prepare.out @@ -23,7 +23,8 @@ CREATE FOREIGN TABLE tenk1 ( stringu2 name, string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); -ALTER TABLE tenk1 SET WITH OIDS; +-- Does not support this command +-- ALTER TABLE tenk1 SET WITH OIDS; CREATE FOREIGN TABLE road ( name text, thepath path @@ -33,45 +34,45 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements; ------+-----------+----------------- (0 rows) -PREPARE q1 AS SELECT * FROM road LIMIT 1; +PREPARE q1 AS SELECT 1 AS a; EXECUTE q1; - name | thepath -------------------------------------+----------------------------------------- - A St | [(-122.0265,37.049),(-122.0271,37.045)] + a +--- + 1 (1 row) SELECT name, statement, parameter_types FROM pg_prepared_statements; - name | statement | parameter_types -------+-------------------------------------------+----------------- - q1 | PREPARE q1 AS SELECT * FROM road LIMIT 1; | {} + name | statement | parameter_types +------+------------------------------+----------------- + q1 | PREPARE q1 AS SELECT 1 AS a; | {} (1 row) -- should fail -PREPARE q1 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q1 AS SELECT 2; ERROR: prepared statement "q1" already exists -- should succeed DEALLOCATE q1; -PREPARE q1 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q1 AS SELECT 2; EXECUTE q1; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + ?column? +---------- + 2 (1 row) -PREPARE q2 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q2 AS SELECT 2 AS b; SELECT name, statement, parameter_types FROM pg_prepared_statements; - name | statement | parameter_types -------+--------------------------------------------+----------------- - q1 | PREPARE q1 AS SELECT * FROM tenk1 LIMIT 1; | {} - q2 | PREPARE q2 AS SELECT * FROM tenk1 LIMIT 1; | {} + name | statement | parameter_types +------+------------------------------+----------------- + q1 | PREPARE q1 AS SELECT 2; | {} + q2 | PREPARE q2 AS SELECT 2 AS b; | {} (2 rows) -- sql92 syntax DEALLOCATE PREPARE q1; SELECT name, statement, parameter_types FROM pg_prepared_statements; - name | statement | parameter_types -------+--------------------------------------------+----------------- - q2 | PREPARE q2 AS SELECT * FROM tenk1 LIMIT 1; | {} + name | statement | parameter_types +------+------------------------------+----------------- + q2 | PREPARE q2 AS SELECT 2 AS b; | {} (1 row) DEALLOCATE PREPARE q2; @@ -91,11 +92,11 @@ EXECUTE q2('postgres'); postgres | f | t (1 row) -PREPARE q3(text, int, float, boolean, oid, smallint) AS +PREPARE q3(text, int, float, boolean, smallint) AS SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR - ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int) + ten = $3::bigint OR true = $4 OR odd = $5::int) ORDER BY unique1; -EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint); +EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 4::bigint); unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- 2 | 2716 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | MAEAAA | AAAAxx @@ -132,19 +133,19 @@ EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint); -- too few params EXECUTE q3('bool'); ERROR: wrong number of parameters for prepared statement "q3" -DETAIL: Expected 6 parameters but got 1. +DETAIL: Expected 5 parameters but got 1. -- too many params -EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true); +EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 4::bigint, true); ERROR: wrong number of parameters for prepared statement "q3" -DETAIL: Expected 6 parameters but got 7. +DETAIL: Expected 5 parameters but got 6. -- wrong param types -EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea'); +EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'bytea'); ERROR: parameter $3 of type boolean cannot be coerced to the expected type double precision HINT: You will need to rewrite or cast the expression. -- invalid type -PREPARE q4(nonexistenttype) AS SELECT * FROM road WHERE name = $1; +PREPARE q4(nonexistenttype) AS SELECT $1; ERROR: type "nonexistenttype" does not exist -LINE 1: PREPARE q4(nonexistenttype) AS SELECT * FROM road WHERE name... +LINE 1: PREPARE q4(nonexistenttype) AS SELECT $1; ^ -- create table as execute PREPARE q5(int, text) AS @@ -172,6 +173,13 @@ SELECT * FROM q5_prep_results; 9961 | 2058 | 1 | 1 | 1 | 1 | 61 | 961 | 1961 | 4961 | 9961 | 122 | 123 | DTAAAA | EBDAAA | OOOOxx (16 rows) +CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') + WITH NO DATA; +SELECT * FROM q5_prep_nodata; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- +(0 rows) + -- unknown or unspecified parameter types: should succeed PREPARE q6 AS SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; @@ -179,22 +187,22 @@ PREPARE q7(unknown) AS SELECT * FROM road WHERE thepath = $1; SELECT name, statement, parameter_types FROM pg_prepared_statements ORDER BY name; - name | statement | parameter_types -------+---------------------------------------------------------------------+-------------------------------------------------------- - q2 | PREPARE q2(text) AS +| {text} - | SELECT datname, datistemplate, datallowconn +| - | FROM pg_database WHERE datname = $1; | - q3 | PREPARE q3(text, int, float, boolean, oid, smallint) AS +| {text,integer,"double precision",boolean,oid,smallint} - | SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR +| - | ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int)+| - | ORDER BY unique1; | - q5 | PREPARE q5(int, text) AS +| {integer,text} - | SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| - | ORDER BY unique1; | - q6 | PREPARE q6 AS +| {integer,name} - | SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; | - q7 | PREPARE q7(unknown) AS +| {path} - | SELECT * FROM road WHERE thepath = $1; | + name | statement | parameter_types +------+------------------------------------------------------------------+---------------------------------------------------- + q2 | PREPARE q2(text) AS +| {text} + | SELECT datname, datistemplate, datallowconn +| + | FROM pg_database WHERE datname = $1; | + q3 | PREPARE q3(text, int, float, boolean, smallint) AS +| {text,integer,"double precision",boolean,smallint} + | SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+| + | ten = $3::bigint OR true = $4 OR odd = $5::int) +| + | ORDER BY unique1; | + q5 | PREPARE q5(int, text) AS +| {integer,text} + | SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| + | ORDER BY unique1; | + q6 | PREPARE q6 AS +| {integer,name} + | SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; | + q7 | PREPARE q7(unknown) AS +| {path} + | SELECT * FROM road WHERE thepath = $1; | (5 rows) -- test DEALLOCATE ALL; diff --git a/expected/extra/select.out b/expected/extra/select.out index e9ae492..689b5f1 100644 --- a/expected/extra/select.out +++ b/expected/extra/select.out @@ -254,6 +254,11 @@ SELECT onek.unique1, onek.string4 FROM onek -- -- test partial btree indexes -- +-- As of 7.2, planner probably won't pick an indexscan without stats, +-- so ANALYZE first. Also, we want to prevent it from picking a bitmapscan +-- followed by sort, because that could hide index ordering problems. +-- +-- ANALYZE onek2; SET enable_seqscan TO off; SET enable_bitmapscan TO off; SET enable_sort TO off; @@ -463,6 +468,27 @@ SELECT p.name, p.age FROM person* p ORDER BY age using >, name; mary | 8 (50 rows) +-- +-- Test some cases involving whole-row Var referencing a subquery +-- +select foo from (select 1 offset 0) as foo; + foo +----- + (1) +(1 row) + +select foo from (select null offset 0) as foo; + foo +----- + () +(1 row) + +select foo from (select 'xyzzy',1,null offset 0) as foo; + foo +------------ + (xyzzy,1,) +(1 row) + -- -- Test VALUES lists -- @@ -498,7 +524,14 @@ select * from onek (3 rows) -- VALUES is also legal as a standalone query or a set-operation member --- VALUES (1,2), (3,4+4), (7,77.7); +VALUES (1,2), (3,4+4), (7,77.7); + column1 | column2 +---------+--------- + 1 | 2 + 3 | 8 + 7 | 77.7 +(3 rows) + VALUES (1,2), (3,4+4), (7,77.7) UNION ALL SELECT 2+2, 57 @@ -541,7 +574,16 @@ SELECT * FROM foo ORDER BY f1 ASC; -- same thing 42 (5 rows) --- SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 NULLS FIRST; + f1 +---- + 1 + 3 + 7 + 10 + 42 +(5 rows) + SELECT * FROM foo ORDER BY f1 DESC; f1 ---- @@ -552,7 +594,16 @@ SELECT * FROM foo ORDER BY f1 DESC; 1 (5 rows) --- SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + f1 +---- + 42 + 10 + 7 + 3 + 1 +(5 rows) + -- check if indexscans do the right things -- CREATE INDEX fooi ON foo (f1); -- SET enable_sort = false; @@ -578,10 +629,11 @@ SELECT * FROM foo ORDER BY f1 DESC; -- partial index is usable explain (costs off) select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; - QUERY PLAN ------------------------ + QUERY PLAN +--------------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 = 'ATAAAA'::name) +(2 rows) select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 @@ -595,14 +647,16 @@ select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; QUERY PLAN ----------------------------------------------- Foreign Scan on onek2 (actual rows=1 loops=1) -(1 row) + Filter: (stringu1 = 'ATAAAA'::name) +(2 rows) explain (costs off) select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; - QUERY PLAN ------------------------ + QUERY PLAN +--------------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 = 'ATAAAA'::name) +(2 rows) select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; unique2 @@ -613,123 +667,176 @@ select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; -- partial index predicate implies clause, so no need for retest explain (costs off) select * from onek2 where unique2 = 11 and stringu1 < 'B'; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 < 'B'::name) +(2 rows) select * from onek2 where unique2 = 11 and stringu1 < 'B'; unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- -(0 rows) + 494 | 11 | 0 | 2 | 4 | 14 | 4 | 94 | 94 | 494 | 494 | 8 | 9 | ATAAAA | LAAAAA | VVVVxx +(1 row) explain (costs off) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 < 'B'::name) +(2 rows) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; unique2 --------- -(0 rows) + 11 +(1 row) -- but if it's an update target, must retest anyway explain (costs off) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; - QUERY PLAN ------------------------------ + QUERY PLAN +---------------------------------------- LockRows -> Foreign Scan on onek2 -(2 rows) + Filter: (stringu1 < 'B'::name) +(3 rows) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; unique2 --------- -(0 rows) + 11 +(1 row) -- partial index is not applicable explain (costs off) select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 < 'C'::name) +(2 rows) select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; unique2 --------- -(0 rows) + 11 +(1 row) -- partial index implies clause, but bitmap scan must recheck predicate anyway SET enable_indexscan TO off; explain (costs off) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 < 'B'::name) +(2 rows) select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; unique2 --------- -(0 rows) + 11 +(1 row) RESET enable_indexscan; -- check multi-index cases too explain (costs off) select unique1, unique2 from onek2 where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (stringu1 < 'B'::name) +(2 rows) select unique1, unique2 from onek2 where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; unique1 | unique2 ---------+--------- -(0 rows) + 494 | 11 + 0 | 998 +(2 rows) explain (costs off) select unique1, unique2 from onek2 where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; - QUERY PLAN ------------------------ + QUERY PLAN +-------------------------------------------------------------------------- Foreign Scan on onek2 -(1 row) + Filter: (((unique2 = 11) AND (stringu1 < 'B'::name)) OR (unique1 = 0)) +(2 rows) select unique1, unique2 from onek2 where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; unique1 | unique2 ---------+--------- + 494 | 11 0 | 998 -(1 row) +(2 rows) -- -- Test some corner cases that have been known to confuse the planner -- -- ORDER BY on a constant doesn't really need any sorting --- SELECT 1 AS x ORDER BY x; +SELECT 1 AS x ORDER BY x; + x +--- + 1 +(1 row) + -- But ORDER BY on a set-valued expression does --- create function sillysrf(int) returns setof int as --- 'values (1),(10),(2),($1)' language sql immutable; --- select sillysrf(42); --- select sillysrf(-1) order by 1; --- drop function sillysrf(int); +create function sillysrf(int) returns setof int as + 'values (1),(10),(2),($1)' language sql immutable; +select sillysrf(42); + sillysrf +---------- + 1 + 10 + 2 + 42 +(4 rows) + +select sillysrf(-1) order by 1; + sillysrf +---------- + -1 + 1 + 2 + 10 +(4 rows) + +drop function sillysrf(int); -- X = X isn't a no-op, it's effectively X IS NOT NULL assuming = is strict -- (see bug #5084) --- select * from (values (2),(null),(1)) v(k) where k = k order by k; --- select * from (values (2),(null),(1)) v(k) where k = k; +select * from (values (2),(null),(1)) v(k) where k = k order by k; + k +--- + 1 + 2 +(2 rows) + +select * from (values (2),(null),(1)) v(k) where k = k; + k +--- + 2 + 1 +(2 rows) + -- Test partitioned tables with no partitions, which should be handled the -- same as the non-inheritance case when expanding its RTE. --- create table list_parted_tbl (a int,b int) partition by list (a); --- create table list_parted_tbl1 partition of list_parted_tbl --- for values in (1) partition by list(b); --- create foreign table list_parted_tbl (a int,b int) server influxdb_svr; --- explain (costs off) select * from list_parted_tbl; --- drop table list_parted_tbl; +create table list_parted_tbl (a int,b int) partition by list (a); +create table list_parted_tbl1 partition of list_parted_tbl + for values in (1) partition by list(b); +explain (costs off) select * from list_parted_tbl; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +drop table list_parted_tbl; DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP SERVER influxdb_svr CASCADE; NOTICE: drop cascades to 5 other objects diff --git a/influxdb_fdw.c b/influxdb_fdw.c index 523cae6..2d0e807 100644 --- a/influxdb_fdw.c +++ b/influxdb_fdw.c @@ -29,7 +29,6 @@ #include "optimizer/paths.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" -#include "optimizer/var.h" #include "optimizer/tlist.h" #include "funcapi.h" #include "utils/builtins.h" @@ -509,7 +508,11 @@ influxdbGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntable startup_cost, total_cost, NIL, /* no pathkeys */ - NULL, /* no outer rel either */ +#if (PG_VERSION_NUM >= 120000) + baserel->lateral_relids, +#else + NULL, /* no outer rel either */ +#endif NULL, /* no extra plan */ NULL)); /* no fdw_private data */ } @@ -1507,7 +1510,6 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, InfluxDBFdwRelationInfo *ifpinfo = input_rel->fdw_private; InfluxDBFdwRelationInfo *fpinfo = grouped_rel->fdw_private; ForeignPath *grouppath; - PathTarget *grouping_target; double rows; int width; Cost startup_cost; @@ -1518,8 +1520,6 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, !root->hasHavingQual) return; - grouping_target = root->upper_targets[UPPERREL_GROUP_AGG]; - /* save the input_rel as outerrel in fpinfo */ fpinfo->outerrel = input_rel; @@ -1545,9 +1545,21 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, fpinfo->total_cost = total_cost; /* Create and add foreign path to the grouping relation. */ +#if (PG_VERSION_NUM >= 120000) + grouppath = create_foreign_upper_path(root, + grouped_rel, + grouped_rel->reltarget, + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + NULL, + NIL); /* no fdw_private */ +#else + grouppath = create_foreignscan_path(root, grouped_rel, - grouping_target, + root->upper_targets[UPPERREL_GROUP_AGG], rows, startup_cost, total_cost, @@ -1555,7 +1567,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, NULL, /* no required_outer */ NULL, NIL); /* no fdw_private */ - +#endif /* Add generated path into grouped_rel by add_path(). */ add_path(grouped_rel, (Path *) grouppath); } diff --git a/influxdb_fdw.h b/influxdb_fdw.h index 3c6daba..4344cc7 100644 --- a/influxdb_fdw.h +++ b/influxdb_fdw.h @@ -17,7 +17,16 @@ #include "foreign/foreign.h" #include "lib/stringinfo.h" + +#if (PG_VERSION_NUM >= 120000) +#include "nodes/pathnodes.h" +#include "utils/float.h" +#include "optimizer/optimizer.h" +#else #include "nodes/relation.h" +#include "optimizer/var.h" +#endif + #include "utils/rel.h" #define WAIT_TIMEOUT 0 diff --git a/influxdb_query.c b/influxdb_query.c index 68e14fb..073ddf4 100644 --- a/influxdb_query.c +++ b/influxdb_query.c @@ -47,7 +47,6 @@ #include "optimizer/paths.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" -#include "optimizer/var.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/plancat.h" diff --git a/init/init_post.txt b/init/init_post.txt index 788e5bd..d8b5c15 100644 --- a/init/init_post.txt +++ b/init/init_post.txt @@ -4647,3 +4647,13 @@ pagg_tab_p3 a=26i,b=46i,c="0059" 86140800000000000 pagg_tab_p3 a=27i,b=47i,c="0059" 86227200000000000 pagg_tab_p3 a=28i,b=48i,c="0059" 86313600000000000 pagg_tab_p3 a=29i,b=49i,c="0059" 86400000000000000 + +loct1_1 f1=2i,f2=2i,f3=2i 0000000000000000001 +loct1_1 f1=4i,f2=4i,f3=4i 0000000000000000002 + +loct2_1 f1=3i,f2=33i,f3=33i 0000000000000000001 +loct2_1 f1=4i,f2=44i,f3=44i 0000000000000000002 +loct2_1 f1=7i,f2=77i,f3=77i 0000000000000000003 + + + diff --git a/init/others.txt b/init/others.txt index 924c7a0..7582780 100644 --- a/init/others.txt +++ b/init/others.txt @@ -82,7 +82,128 @@ int4_tbl3 f1=3i 0000000000000000005 int4_tbl3 f1=3i 0000000000000000006 int4_tbl3 f1=4i 0000000000000000007 +int4_tbl4 f1=1i 0000000000000000001 +int4_tbl4 f1=1i 0000000000000000002 +int4_tbl4 f1=2i 0000000000000000003 +int4_tbl4 f1=2i 0000000000000000004 +int4_tbl4 f1=3i 0000000000000000005 +int4_tbl4 f1=3i 0000000000000000006 +int4_tbl4 f1=4i 0000000000000000007 +int4_tbl4 f1=5i 0000000000000000008 + int8_tbl2 q1=1i,q2=4i 0000000000000000001 int8_tbl2 q1=2i,q2=3i 0000000000000000002 int8_tbl2 q1=3i,q2=1i 0000000000000000003 int8_tbl2 q1=4i,q2=2i 0000000000000000004 + + +# For test aggregates.sql + +string_agg1 a1="aaaa",a2="" 0000000000000000001 +string_agg1 a1="bbbb",a2="" 0000000000000000002 +string_agg1 a1="cccc",a2="" 0000000000000000003 + +string_agg2 a1="aaaa",a2="" 0000000000000000001 +string_agg2 a2="bbbb" 0000000000000000002 +string_agg2 a1="bbbb",a2="" 0000000000000000003 +string_agg2 a1="cccc",a2="" 0000000000000000004 + +string_agg3 a2="aaaa" 0000000000000000001 +string_agg3 a2="bbbb" 0000000000000000002 +string_agg3 a1="bbbb",a2="" 0000000000000000003 +string_agg3 a1="cccc",a2="" 0000000000000000004 + +string_agg4 a2="aaaa" 0000000000000000001 +string_agg4 a2="bbbb" 0000000000000000002 + +generate_series1 a=1i 00000000000001 +generate_series1 a=2i 00000000000002 +generate_series1 a=3i 00000000000003 + +infinite1 id=1i,a="1" 00000000000001 +infinite1 id=1i,a="infinity" 00000000000002 +infinite1 id=2i,a="infinity" 00000000000003 +infinite1 id=2i,a="1" 00000000000004 +infinite1 id=3i,a="infinity" 00000000000005 +infinite1 id=3i,a="infinity" 00000000000006 +infinite1 id=4i,a="-infinity" 00000000000007 +infinite1 id=4i,a="infinity" 00000000000008 + +large_input1 id=1i,a="100000003" 00000000000001 +large_input1 id=1i,a="100000004" 00000000000002 +large_input1 id=1i,a="100000006" 00000000000003 +large_input1 id=1i,a="100000007" 00000000000004 +large_input1 id=2i,a="7000000000005" 00000000000005 +large_input1 id=2i,a="7000000000007" 00000000000006 + +regr_test1 x=10,y=150 00000000000001 +regr_test1 x=20,y=250 00000000000002 +regr_test1 x=30,y=350 00000000000003 +regr_test1 x=80,y=540 00000000000004 +regr_test1 x=100,y=200 00000000000005 + +float8_arr1 id=1i,x="{4,140,2900}" 00000000000001 +float8_arr1 id=2i,x="{4,140,2900,1290,83075,15050}" 00000000000002 +float8_arr1 id=3i,x="{3,60,200}",y="{0,0,0}" 00000000000003 +float8_arr1 id=4i,x="{0,0,0}",y="{2,180,200}" 00000000000004 +float8_arr1 id=5i,x="{3,60,200}",y="{2,180,200}" 00000000000005 +float8_arr1 id=6i,x="{3,60,200,750,20000,2000}",y="{0,0,0,0,0,0}" 00000000000006 +float8_arr1 id=7i,x="{0,0,0,0,0,0}",y="{2,180,200,740,57800,-3400}" 00000000000007 +float8_arr1 id=8i,x="{3,60,200,750,20000,200}",y="{2,180,200,740,57800,-3400}" 00000000000008 + +boolean1 x2=TRUE,x3=FALSE,y4=TRUE,y5=FALSE,x6=TRUE,y6=TRUE,x7=FALSE,y7=TRUE,x8=FALSE,y8=TRUE,x9=FALSE,y9=FALSE 00000000000001 + +generate_series2 a=1i 00000000000001 +generate_series2 a=2i 00000000000002 +generate_series2 a=3i 00000000000003 +generate_series2 a=4i 00000000000004 +generate_series2 a=5i 00000000000005 +generate_series2 a=6i 00000000000006 + +percentile_disc1 x="{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}" 00000000000001 + +pg_collation1 x="fred" 00000000000001 +pg_collation1 x="jim" 00000000000002 + +test_rank1 x=1i 00000000000001 +test_rank1 x=1i 00000000000002 +test_rank1 x=2i 00000000000003 +test_rank1 x=2i 00000000000004 +test_rank1 x=3i 00000000000005 +test_rank1 x=3i 00000000000006 +test_rank1 x=4i 00000000000007 + +generate_series3 x=1i 00000000000001 +generate_series3 x=2i 00000000000002 +generate_series3 x=3i 00000000000003 +generate_series3 x=4i 00000000000004 +generate_series3 x=5i 00000000000005 +generate_series3 x=6i 00000000000006 +generate_series3 x=7i 00000000000007 + +my_avg1 one=1i 00000000000001 +my_avg1 one=3i 00000000000002 + +my_avg2 one=1i,two=2i 00000000000001 +my_avg2 one=3i,two=4i 00000000000002 + +percentile_cont1 a=1i 00000000000001 +percentile_cont1 a=3i 00000000000002 +percentile_cont1 a=5i 00000000000003 +percentile_cont1 a=7i 00000000000004 + +my_sum1 one=1i 00000000000001 +my_sum1 one=2i 00000000000002 +my_sum1 one=3i 00000000000003 +my_sum1 one=4i 00000000000004 + +dense_rank1 x=1i 00000000000001 +dense_rank1 x=1i 00000000000002 +dense_rank1 x=2i 00000000000003 +dense_rank1 x=2i 00000000000004 +dense_rank1 x=3i 00000000000005 +dense_rank1 x=3i 00000000000006 + + +# End for test aggregates.sql + diff --git a/sql/extra/aggregates.sql b/sql/extra/aggregates.sql index e77ad3d..10dd946 100644 --- a/sql/extra/aggregates.sql +++ b/sql/extra/aggregates.sql @@ -67,6 +67,7 @@ CREATE FOREIGN TABLE INT8_TBL2 ( CREATE FOREIGN TABLE INT4_TBL (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE INT4_TBL2 (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE INT4_TBL3 (f1 int4) SERVER influxdb_svr; +CREATE FOREIGN TABLE INT4_TBL4 (f1 int4) SERVER influxdb_svr; CREATE FOREIGN TABLE multi_arg_agg (a int, b int, c text) SERVER influxdb_svr; CREATE FOREIGN TABLE multi_arg_agg2 (a int, b int, c text) SERVER influxdb_svr; @@ -79,6 +80,9 @@ CREATE FOREIGN TABLE FLOAT8_TBL (f1 float8) SERVER influxdb_svr; -- AGGREGATES -- +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; + SELECT avg(four) AS avg_1 FROM onek; SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100; @@ -117,16 +121,39 @@ SELECT var_pop(1.0), var_samp(2.0); SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); -- verify correct results for null and NaN inputs --- select sum(null::int4) from generate_series(1,3); --- select sum(null::int8) from generate_series(1,3); --- select sum(null::numeric) from generate_series(1,3); --- select sum(null::float8) from generate_series(1,3); --- select avg(null::int4) from generate_series(1,3); --- select avg(null::int8) from generate_series(1,3); --- select avg(null::numeric) from generate_series(1,3); --- select avg(null::float8) from generate_series(1,3); --- select sum('NaN'::numeric) from generate_series(1,3); --- select avg('NaN'::numeric) from generate_series(1,3); +create foreign table generate_series1(a int) server influxdb_svr; +select sum(null::int4) from generate_series1; +select sum(null::int8) from generate_series1; +select sum(null::numeric) from generate_series1; +select sum(null::float8) from generate_series1; +select avg(null::int4) from generate_series1; +select avg(null::int8) from generate_series1; +select avg(null::numeric) from generate_series1; +select avg(null::float8) from generate_series1; +select sum('NaN'::numeric) from generate_series1; +select avg('NaN'::numeric) from generate_series1; + +-- verify correct results for infinite inputs +create foreign table infinite1(id int, a text) server influxdb_svr; +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 1; + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 2; + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 3; + +SELECT avg(a::float8), var_pop(a::float8) +FROM infinite1 WHERE id = 4; + +-- test accuracy with a large input offset +create foreign table large_input1(id int, a int) server influxdb_svr; +SELECT avg(a::float8), var_pop(a::float8) +FROM large_input1 WHERE id=1; + +SELECT avg(a::float8), var_pop(a::float8) +FROM large_input1 WHERE id=2; -- SQL2003 binary aggregates SELECT regr_count(b, a) FROM aggtest; @@ -139,6 +166,28 @@ SELECT regr_slope(b, a), regr_intercept(b, a) FROM aggtest; SELECT covar_pop(b, a), covar_samp(b, a) FROM aggtest; SELECT corr(b, a) FROM aggtest; +-- test accum and combine functions directly +CREATE FOREIGN TABLE regr_test1 (x float8, y float8) server influxdb_svr; +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (10,20,30,80); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1; + +CREATE FOREIGN TABLE float8_arr1 (id int, x text, y text) server influxdb_svr; +SELECT float8_accum(x::float8[], 100) FROM float8_arr1 WHERE id=1; +SELECT float8_regr_accum(x::float8[], 200, 100) FROM float8_arr1 WHERE id=2; +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (10,20,30); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test1 WHERE x IN (80,100); +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=3; +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=4; +SELECT float8_combine(x::float8[], y::float8[]) FROM float8_arr1 WHERE id=5; +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=6; +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=7; +SELECT float8_regr_combine(x::float8[],y::float8[]) FROM float8_arr1 WHERE id=8; + +-- test count, distinct SELECT count(four) AS cnt_1000 FROM onek; SELECT count(DISTINCT four) AS cnt_4 FROM onek; @@ -210,31 +259,32 @@ having exists (select 1 from onek b -- Test handling of sublinks within outer-level aggregates. -- Per bug report from Daniel Grace. --- select --- (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) --- from tenk1 o; +-- this test +--select +-- (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) +--from tenk1 o; -- Test handling of Params within aggregate arguments in hashed aggregation. -- Per bug report from Jeevan Chalke. explain (verbose, costs off) -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss +select s1.a, ss.a, sm +from generate_series1 s1, + lateral (select s2.a, sum(s1.a + s2.a) sm + from generate_series1 s2 group by s2.a) ss order by 1, 2; -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss +select s1.a, ss.a, sm +from generate_series1 s1, + lateral (select s2.a, sum(s1.a + s2.a) sm + from generate_series1 s2 group by s2.a) ss order by 1, 2; explain (verbose, costs off) -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; +select array(select sum(x.a+y.a) s + from generate_series1 y group by y.a order by s) + from generate_series1 x; +select array(select sum(x.a+y.a) s + from generate_series1 y group by y.a order by s) + from generate_series1 x; -- -- test for bitwise integer aggregates @@ -283,34 +333,39 @@ FROM bitwise_test; -- test boolean aggregates -- -- first test all possible transition and final states +CREATE FOREIGN TABLE boolean1 (x1 BOOL, y1 BOOL , x2 BOOL, y2 BOOL, + x3 BOOL, y3 BOOL, x4 BOOL, y4 BOOL, + x5 BOOL, y5 BOOL, x6 BOOL, y6 BOOL, + x7 BOOL, y7 BOOL, x8 BOOL, y8 BOOL, + x9 BOOL, y9 BOOL) SERVER influxdb_svr; SELECT -- boolean and transitions -- null because strict - booland_statefunc(NULL, NULL) IS NULL AS "t", - booland_statefunc(TRUE, NULL) IS NULL AS "t", - booland_statefunc(FALSE, NULL) IS NULL AS "t", - booland_statefunc(NULL, TRUE) IS NULL AS "t", - booland_statefunc(NULL, FALSE) IS NULL AS "t", + booland_statefunc(x1, y1) IS NULL AS "t", + booland_statefunc(x2, y2) IS NULL AS "t", + booland_statefunc(x3, y3) IS NULL AS "t", + booland_statefunc(x4, y4) IS NULL AS "t", + booland_statefunc(x5, y5) IS NULL AS "t", -- and actual computations - booland_statefunc(TRUE, TRUE) AS "t", - NOT booland_statefunc(TRUE, FALSE) AS "t", - NOT booland_statefunc(FALSE, TRUE) AS "t", - NOT booland_statefunc(FALSE, FALSE) AS "t"; + booland_statefunc(x6, y6) AS "t", + NOT booland_statefunc(x7, y7) AS "t", + NOT booland_statefunc(x8, y8) AS "t", + NOT booland_statefunc(x9, y9) AS "t" FROM boolean1; SELECT -- boolean or transitions -- null because strict - boolor_statefunc(NULL, NULL) IS NULL AS "t", - boolor_statefunc(TRUE, NULL) IS NULL AS "t", - boolor_statefunc(FALSE, NULL) IS NULL AS "t", - boolor_statefunc(NULL, TRUE) IS NULL AS "t", - boolor_statefunc(NULL, FALSE) IS NULL AS "t", + boolor_statefunc(x1, y1) IS NULL AS "t", + boolor_statefunc(x2, y2) IS NULL AS "t", + boolor_statefunc(x3, y3) IS NULL AS "t", + boolor_statefunc(x4, y4) IS NULL AS "t", + boolor_statefunc(x5, y5) IS NULL AS "t", -- actual computations - boolor_statefunc(TRUE, TRUE) AS "t", - boolor_statefunc(TRUE, FALSE) AS "t", - boolor_statefunc(FALSE, TRUE) AS "t", - NOT boolor_statefunc(FALSE, FALSE) AS "t"; + boolor_statefunc(x6, y6) AS "t", + boolor_statefunc(x7, y7) AS "t", + boolor_statefunc(x8, y8) AS "t", + NOT boolor_statefunc(x9, y9) AS "t" FROM boolean1; CREATE FOREIGN TABLE bool_test_empty ( b1 BOOL, @@ -594,31 +649,23 @@ select aggfns(distinct a,b,c order by a,b,i,c) from multi_arg_agg2, generate_ser select aggfns(distinct a,a,c order by a,b) from multi_arg_agg2, generate_series(1,2) i; -- string_agg tests --- begin; --- delete from varchar_tbl; --- insert into varchar_tbl values ('aaaa'),('bbbb'),('cccc'); --- select string_agg(f1,',') from varchar_tbl; - --- delete from varchar_tbl; --- insert into varchar_tbl values ('aaaa'),(null),('bbbb'),('cccc'); --- select string_agg(f1,',') from varchar_tbl; - --- delete from varchar_tbl; --- insert into varchar_tbl values (null),(null),('bbbb'),('cccc'); --- select string_agg(f1,'AB') from varchar_tbl; +create foreign table string_agg1(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg2(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg3(a1 text, a2 text) server influxdb_svr; +create foreign table string_agg4(a1 text, a2 text) server influxdb_svr; --- delete from varchar_tbl; --- insert into varchar_tbl values (null),(null); --- select string_agg(f1,',') from varchar_tbl; --- rollback; +select string_agg(a1,',') from string_agg1; +select string_agg(a1,',') from string_agg2; +select string_agg(a1,'AB') from string_agg3; +select string_agg(a1,',') from string_agg4; -- check some implicit casting cases, as per bug #5564 - select string_agg(distinct f1, ',' order by f1) from varchar_tbl; -- ok select string_agg(distinct f1::text, ',' order by f1) from varchar_tbl; -- not ok select string_agg(distinct f1, ',' order by f1::text) from varchar_tbl; -- not ok select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl; -- ok +-- InfluxDB does not support binary data -- string_agg bytea tests -- create foreign table bytea_test_table(v bytea) server influxdb_svr; @@ -714,7 +761,7 @@ select percentile_disc(0.5) within group (order by thousand) from tenk1; begin; select rank(3) within group (order by f1) from INT4_TBL3; select cume_dist(3) within group (order by f1) from INT4_TBL3; -select percent_rank(3) within group (order by f1) from INT4_TBL3; +select percent_rank(3) within group (order by f1) from INT4_TBL4; select dense_rank(3) within group (order by f1) from INT4_TBL3; rollback; @@ -724,43 +771,64 @@ select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand from tenk1; select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1; --- select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by x) --- from generate_series(1,6) x; +create foreign table generate_series2 (a int) server influxdb_svr; +select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by a) +from generate_series2; select ten, mode() within group (order by string4) from tenk1 group by ten; -select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) -from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); +create foreign table percentile_disc1(x text) server influxdb_svr; +select percentile_disc(array[0.25,0.5,0.75]) within group (order by unnest) +from (select unnest(x::text[]) from percentile_disc1) y; -- check collation propagates up in suitable cases: +create foreign table pg_collation1 (x text) server influxdb_svr; select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) - from (values ('fred'),('jim')) v(x); + from pg_collation1; + +-- test ordered-set aggs using built-in support functions +create aggregate test_percentile_disc(float8 ORDER BY anyelement) ( + stype = internal, + sfunc = ordered_set_transition, + finalfunc = percentile_disc_final, + finalfunc_extra = true, + finalfunc_modify = read_write +); + +create aggregate test_rank(VARIADIC "any" ORDER BY VARIADIC "any") ( + stype = internal, + sfunc = ordered_set_transition_multi, + finalfunc = rank_final, + finalfunc_extra = true, + hypothetical +); -- ordered-set aggs created with CREATE AGGREGATE --- select test_rank(3) within group (order by x) --- from (values (1),(1),(2),(2),(3),(3),(4)) v(x); --- select test_percentile_disc(0.5) within group (order by thousand) from tenk1; +create foreign table test_rank1 (x int) server influxdb_svr; +select test_rank(3) within group (order by x) from test_rank1; +select test_percentile_disc(0.5) within group (order by thousand) from tenk1; -- ordered-set aggs can't use ungrouped vars in direct args: -select rank(x) within group (order by x) from generate_series(1,5) x; +create foreign table generate_series3 (x int) server influxdb_svr; +select rank(x) within group (order by x) from generate_series3 x; -- outer-level agg can't use a grouped arg of a lower level, either: select array(select percentile_disc(a) within group (order by x) from (values (0.3),(0.7)) v(a) group by a) - from generate_series(1,5) g(x); + from generate_series3; -- agg in the direct args is a grouping violation, too: -select rank(sum(x)) within group (order by x) from generate_series(1,5) x; +select rank(sum(x)) within group (order by x) from generate_series3 x; -- hypothetical-set type unification and argument-count failures: --- select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); +select rank(3) within group (order by x) from pg_collation1; select rank(3) within group (order by stringu1,stringu2) from tenk1; -select rank('fred') within group (order by x) from generate_series(1,5) x; --- select rank('adam'::text collate "C") within group (order by x collate "POSIX") --- from (values ('fred'),('jim')) v(x); +select rank('fred') within group (order by x) from generate_series3 x; +select rank('adam'::text collate "C") within group (order by x collate "POSIX") + from pg_collation1; -- hypothetical-set type unification successes: -select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); -select rank('3') within group (order by x) from generate_series(1,5) x; +select rank('adam'::varchar) within group (order by x) from pg_collation1; +select rank('3') within group (order by x) from generate_series3 x; -- divide by zero check select percent_rank(0) within group (order by x) from generate_series(1,0) x; @@ -778,6 +846,14 @@ select pg_get_viewdef('aggordview1'); select * from aggordview1 order by ten; drop view aggordview1; +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + -- variadic aggregates select least_agg(q1,q2) from int8_tbl; select least_agg(variadic array[q1,q2]) from int8_tbl; @@ -847,39 +923,42 @@ create aggregate my_sum(int4) ); -- aggregate state should be shared as aggs are the same. -select my_avg(one),my_avg(one) from (values(1),(3)) t(one); +create foreign table my_avg1 (one int) server influxdb_svr; +select my_avg(one),my_avg(one) from my_avg1; -- aggregate state should be shared as transfn is the same for both aggs. -select my_avg(one),my_sum(one) from (values(1),(3)) t(one); +select my_avg(one),my_sum(one) from my_avg1; -- same as previous one, but with DISTINCT, which requires sorting the input. -select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one); +select my_avg(distinct one),my_sum(distinct one) from my_avg1; -- shouldn't share states due to the distinctness not matching. -select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one); +select my_avg(distinct one),my_sum(one) from my_avg1; -- shouldn't share states due to the filter clause not matching. -select my_avg(one) filter (where one > 1),my_sum(one) from (values(1),(3)) t(one); +select my_avg(one) filter (where one > 1),my_sum(one) from my_avg1; -- this should not share the state due to different input columns. -select my_avg(one),my_sum(two) from (values(1,2),(3,4)) t(one,two); +create foreign table my_avg2(one int, two int) server influxdb_svr; +select my_avg(one),my_sum(two) from my_avg2; -- exercise cases where OSAs share state +create foreign table percentile_cont1( a int) server influxdb_svr; select percentile_cont(0.5) within group (order by a), percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); +from percentile_cont1; select percentile_cont(0.25) within group (order by a), percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); +from percentile_cont1; -- these can't share state currently select rank(4) within group (order by a), dense_rank(4) within group (order by a) -from (values(1),(3),(5),(7)) t(a); +from percentile_cont1; -- test that aggs with the same sfunc and initcond share the same agg state create aggregate my_sum_init(int4) @@ -907,10 +986,10 @@ create aggregate my_avg_init2(int4) ); -- state should be shared if INITCONDs are matching -select my_sum_init(one),my_avg_init(one) from (values(1),(3)) t(one); +select my_sum_init(one),my_avg_init(one) from my_avg1; -- Varying INITCONDs should cause the states not to be shared. -select my_sum_init(one),my_avg_init2(one) from (values(1),(3)) t(one); +select my_sum_init(one),my_avg_init2(one) from my_avg1; rollback; @@ -963,7 +1042,8 @@ create aggregate my_half_sum(int4) ); -- Agg state should be shared even though my_sum has no finalfn -select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one); +create foreign table my_sum1(one int) server influxdb_svr; +select my_sum(one),my_half_sum(one) from my_sum1; rollback; @@ -1042,15 +1122,39 @@ SET enable_indexonlyscan = off; -- variance(int4) covers numeric_poly_combine -- sum(int8) covers int8_avg_combine -EXPLAIN (COSTS OFF) - SELECT variance(unique1::int4), sum(unique1::int8) FROM tenk1; +-- regr_count(float8, float8) covers int8inc_float8_float8 and aggregates with > 1 arg +EXPLAIN (COSTS OFF, VERBOSE) + SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) FROM tenk1; -SELECT variance(unique1::int4), sum(unique1::int8) FROM tenk1; +SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) FROM tenk1; ROLLBACK; -- test coverage for dense_rank -SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM (VALUES (1),(1),(2),(2),(3),(3)) v(x) GROUP BY (x) ORDER BY 1; +create foreign table dense_rank1 (x int) server influxdb_svr; +SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM dense_rank1 GROUP BY (x) ORDER BY 1; + + +-- Ensure that the STRICT checks for aggregates does not take NULLness +-- of ORDER BY columns into account. See bug report around +-- 2a505161-2727-2473-7c46-591ed108ac52@email.cz +SELECT min(x ORDER BY y) FROM (VALUES(1, NULL)) AS d(x,y); +SELECT min(x ORDER BY y) FROM (VALUES(1, 2)) AS d(x,y); + +-- check collation-sensitive matching between grouping expressions +select v||'a', case v||'a' when 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; +select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; + +-- Make sure that generation of HashAggregate for uniqification purposes +-- does not lead to array overflow due to unexpected duplicate hash keys +-- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com +explain (costs off) + select 1 from tenk1 + where (hundred, thousand) in (select twothousand, twothousand from onek); -- Clean up DO $d$ diff --git a/sql/extra/influxdb_fdw_post.sql b/sql/extra/influxdb_fdw_post.sql index bf60bec..782eef0 100644 --- a/sql/extra/influxdb_fdw_post.sql +++ b/sql/extra/influxdb_fdw_post.sql @@ -20,6 +20,7 @@ CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr OPTIONS (user 'user', p CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr2 OPTIONS (user 'user', password 'pass'); -- import time column as timestamp and text type +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE SCHEMA "S 1"; IMPORT FOREIGN SCHEMA public FROM SERVER influxdb_svr INTO "S 1"; @@ -32,6 +33,8 @@ CREATE FOREIGN TABLE ft1 ( c1 int NOT NULL, c2 int NOT NULL, c3 text, + -- c4 timestamptz, + -- c5 timestamp, c6 varchar(10), c7 char(10) default 'ft1', c8 text @@ -44,6 +47,8 @@ CREATE FOREIGN TABLE ft2 ( c2 int NOT NULL, cx int, c3 text, + -- c4 timestamptz, + -- c5 timestamp, c6 varchar(10), c7 char(10) default 'ft2', c8 text @@ -74,11 +79,43 @@ CREATE FOREIGN TABLE ft6 ( -- requiressl, krbsrvname and gsslib are omitted because they depend on -- configure options ALTER SERVER testserver1 OPTIONS ( + -- use_remote_estimate 'false', + -- updatable 'true', + -- fdw_startup_cost '123.456', + -- fdw_tuple_cost '0.123', + -- service 'value', + -- connect_timeout 'value', dbname 'value', host 'value', port 'value' + -- hostaddr 'value', + -- client_encoding 'value', + -- application_name 'value', + -- fallback_application_name 'value', + -- keepalives 'value', + -- keepalives_idle 'value', + -- keepalives_interval 'value', + -- tcp_user_timeout 'value', + -- requiressl 'value', + -- sslcompression 'value', + -- sslmode 'value', + -- sslcert 'value', + -- sslkey 'value', + -- sslrootcert 'value', + -- sslcrl 'value' + -- --requirepeer 'value', + -- krbsrvname 'value', + -- gsslib 'value', + -- replication 'value' ); +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); + +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +ALTER SERVER testserver1 OPTIONS (DROP extensions); + ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (DROP user, DROP password); @@ -118,6 +155,12 @@ ALTER USER MAPPING FOR CURRENT_USER SERVER influxdb_svr SELECT c3, time FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work \set VERBOSITY default +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); + -- =================================================================== -- simple queries -- =================================================================== @@ -194,7 +237,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- Op EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r) EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr -EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote -- parameterized remote path for foreign table @@ -247,9 +290,15 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + -- but let's put them in an extension ... ALTER EXTENSION influxdb_fdw ADD FUNCTION influxdb_fdw_abs(int); ALTER EXTENSION influxdb_fdw ADD OPERATOR === (int, int); +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- ... now they can be shipped EXPLAIN (VERBOSE, COSTS OFF) @@ -259,9 +308,18 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + -- =================================================================== -- JOIN queries -- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; -- join two tables EXPLAIN (VERBOSE, COSTS OFF) @@ -364,6 +422,12 @@ SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 -- full outer join + WHERE clause with shippable extensions set EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE influxdb_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- skip, influxdb does not have option 'extensions' +-- ALTER SERVER influxdb_svr OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +-- EXPLAIN (VERBOSE, COSTS OFF) +-- SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- join two tables with FOR UPDATE clause -- tests whole-row reference for row marks EXPLAIN (VERBOSE, COSTS OFF) @@ -381,8 +445,8 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; -- join in CTE EXPLAIN (VERBOSE, COSTS OFF) -WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; -WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; -- ctid with whole-row reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; @@ -394,7 +458,7 @@ SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; --- CROSS JOIN, not pushed down +-- CROSS JOIN can be pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; @@ -425,7 +489,6 @@ SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 -- join with lateral reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C1" FROM "S 1"."T1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C1" OFFSET 10 LIMIT 10; --- TODO SELECT t1."C1" FROM "S 1"."T1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C1" OFFSET 10 LIMIT 10; -- non-Var items in targetlist of the nullable rel of a join preventing @@ -441,21 +504,26 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; -- join with nullable side with some columns with null values +-- UPDATE ft5 SET c3 = null where c1 % 9 = 0; EXPLAIN (VERBOSE, COSTS OFF) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; -- multi-way join involving multiple merge joins -- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; SET enable_nestloop TO false; SET enable_hashjoin TO false; EXPLAIN (VERBOSE, COSTS OFF) -SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 - AND ft1.c2 = ft5.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; -SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 - AND ft1.c2 = ft5.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; RESET enable_nestloop; RESET enable_hashjoin; +DROP TABLE local_tbl; -- check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; @@ -487,6 +555,7 @@ ALTER VIEW v4 OWNER TO regress_view_owner; DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; + -- =================================================================== -- Aggregate and grouping queries -- =================================================================== @@ -496,6 +565,10 @@ explain (verbose, costs off) select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + -- Aggregate is not pushed down as aggregation contains random() explain (verbose, costs off) select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; @@ -553,6 +626,16 @@ select count(*) from (select time, count(c1) from ft1 group by time, sqrt(c2) ha explain (verbose, costs off) select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates @@ -616,11 +699,11 @@ select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; -- User defined function for user defined aggregate, VARIADIC -create function least_accum(anyelement, variadic anyarray) +create function least_accum1(anyelement, variadic anyarray) returns anyelement language sql as 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; -create aggregate least_agg(variadic items anyarray) ( - stype = anyelement, sfunc = least_accum +create aggregate least_agg1(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum1 ); -- Disable hash aggregation for plan stability. @@ -628,29 +711,29 @@ set enable_hashagg to false; -- Not pushed down due to user defined aggregate explain (verbose, costs off) -select c2, least_agg(c1) from ft1 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 group by c2 order by c2; -- Add function and aggregate into extension -alter extension influxdb_fdw add function least_accum(anyelement, variadic anyarray); -alter extension influxdb_fdw add aggregate least_agg(variadic items anyarray); +alter extension influxdb_fdw add function least_accum1(anyelement, variadic anyarray); +alter extension influxdb_fdw add aggregate least_agg1(variadic items anyarray); -- Now aggregate will be pushed. Aggregate will display VARIADIC argument. explain (verbose, costs off) -select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; -select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 where c2 < 100 group by c2 order by c2; -- Remove function and aggregate from extension -alter extension influxdb_fdw drop function least_accum(anyelement, variadic anyarray); -alter extension influxdb_fdw drop aggregate least_agg(variadic items anyarray); +alter extension influxdb_fdw drop function least_accum1(anyelement, variadic anyarray); +alter extension influxdb_fdw drop aggregate least_agg1(variadic items anyarray); -- Not pushed down as we have dropped objects from extension. explain (verbose, costs off) -select c2, least_agg(c1) from ft1 group by c2 order by c2; +select c2, least_agg1(c1) from ft1 group by c2 order by c2; -- Cleanup reset enable_hashagg; -drop aggregate least_agg(variadic items anyarray); -drop function least_accum(anyelement, variadic anyarray); +drop aggregate least_agg1(variadic items anyarray); +drop function least_accum1(anyelement, variadic anyarray); -- Testing USING OPERATOR() in ORDER BY within aggregate. @@ -692,6 +775,9 @@ create operator class my_op_class for type int using btree family my_op_family a explain (verbose, costs off) select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +-- Update local stats on ft2 +ANALYZE ft2; + -- Add into extension alter extension influxdb_fdw add operator class my_op_class using btree; alter extension influxdb_fdw add function my_op_cmp(a int, b int); @@ -759,6 +845,32 @@ select c2, sum from "S 1"."T1" t1, lateral (select sum(t2.c1 + t1."C1") sum from select c2, sum from "S 1"."T1" t1, lateral (select sum(t2.c1 + t1."C1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C1" < 100 order by 1; reset enable_hashagg; +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T1" AS ref_0, + LATERAL ( + SELECT ref_0."C1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C1"; + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T1" AS ref_0, + LATERAL ( + SELECT ref_0."C1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C1"; + -- Check with placeHolderVars explain (verbose, costs off) select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); @@ -796,6 +908,7 @@ explain (verbose, costs off) select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + -- =================================================================== -- parameterized queries -- =================================================================== @@ -836,17 +949,24 @@ EXECUTE st5('foo', 1); -- altering FDW options requires replanning PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +-- influxdb doesnot support INSERT +-- PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T1" RENAME TO "T0"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table 'T0'); EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; EXECUTE st6; +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T0" RENAME TO "T1"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table 'T1'); -EXECUTE st6; PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; -EXECUTE st8; +-- Skip, Influxdb does not support extensions +-- ALTER SERVER loopback OPTIONS (DROP extensions); +-- EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +-- EXECUTE st8; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- cleanup DEALLOCATE st1; @@ -855,6 +975,7 @@ DEALLOCATE st3; DEALLOCATE st4; DEALLOCATE st5; DEALLOCATE st6; +-- DEALLOCATE st7; DEALLOCATE st8; -- System columns, except ctid and oid, should not be sent to remote @@ -865,6 +986,9 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid, * FROM ft1 t1 LIMIT 1; SELECT ctid, * FROM ft1 t1 LIMIT 1; @@ -892,7 +1016,7 @@ SELECT ft1.c1, ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = SELECT ft1.c1, ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; -SELECT * FROM ft1 WHERE c1 = 1; +SELECT * FROM ft1 WHERE c1 = 1; -- Should work -- =================================================================== -- subtransaction @@ -934,6 +1058,152 @@ explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; explain (verbose, costs off) select * from ft3 f, loct3 l where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +-- Skip +-- EXPLAIN (verbose, costs off) +-- INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +-- INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +-- INSERT INTO ft2 (c1,c2,c3) +-- VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down +-- UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down +-- UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT +-- FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down +-- UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT +-- FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down +-- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down +-- DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +-- SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; +-- EXPLAIN (verbose, costs off) +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +-- UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +-- DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + +-- Test UPDATE/DELETE with RETURNING on a three-table join +-- INSERT INTO ft2 (c1,c2,c3) +-- SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'foo' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 +-- RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +-- UPDATE ft2 SET c3 = 'foo' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 +-- RETURNING ft2, ft2.*, ft4, ft4.*; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 +-- USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 +-- RETURNING 100; -- can be pushed down +-- DELETE FROM ft2 +-- USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 +-- RETURNING 100; +-- DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +-- ALTER SERVER loopback OPTIONS (DROP extensions); +-- INSERT INTO ft2 (c1,c2,c3) +-- SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +-- UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +-- EXPLAIN (verbose, costs off) +-- UPDATE ft2 SET c3 = 'baz' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 +-- RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +-- UPDATE ft2 SET c3 = 'baz' +-- FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 +-- RETURNING ft2.*, ft4.*, ft5.*; +-- EXPLAIN (verbose, costs off) +-- DELETE FROM ft2 +-- USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 +-- RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +-- DELETE FROM ft2 +-- USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) +-- WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 +-- RETURNING ft2.c1, ft2.c2, ft2.c3; +-- DELETE FROM ft2 WHERE ft2.c1 > 2000; +-- ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- Test that trigger on remote table works as expected +-- CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +-- BEGIN +-- NEW.c3 = NEW.c3 || '_trig_update'; +-- RETURN NEW; +-- END; +-- $$ LANGUAGE plpgsql; +-- CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE +-- ON "S 1"."T1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); + +-- INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; +-- INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; +-- UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + +-- Test errors thrown on remote side during update +-- ALTER TABLE "S 1"."T1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); + +-- INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +-- INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +-- INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +-- UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive + +-- Test savepoint/rollback behavior +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; +-- begin; +-- update ft2 set c2 = 42 where c2 = 0; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s1; +-- update ft2 set c2 = 44 where c2 = 4; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s1; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s2; +-- update ft2 set c2 = 46 where c2 = 6; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- rollback to savepoint s2; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s2; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- savepoint s3; +-- update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +-- rollback to savepoint s3; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- release savepoint s3; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- none of the above is committed yet remotely +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; +-- commit; +-- select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- select c2, count(*) from "S 1"."T1" where c2 < 500 group by 1 order by 1; + +-- VACUUM ANALYZE "S 1"."T1"; + -- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs -- FIRST behavior here. -- ORDER BY DESC NULLS LAST options @@ -947,20 +1217,1004 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 O SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; -- =================================================================== --- test tuple routing for foreign-table partitions +-- test check constraints +-- =================================================================== + +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +-- INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +-- UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +-- INSERT INTO ft1(c1, c2) VALUES(1111, 2); +-- UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + -- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== + +CREATE FUNCTION row_before_insupd_trigfunc() +RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; +RETURN NEW; END$$ LANGUAGE plpgsql; + +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger +BEFORE INSERT OR UPDATE ON base_tbl +FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +-- skip, Influxdb does not support INSERT. However, we keep this test case +-- to test locally +--CREATE FOREIGN TABLE foreign_tbl (a int, b int) +-- SERVER influxdb_svr OPTIONS (table 'base_tbl'); +CREATE VIEW rw_view AS SELECT * FROM base_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM base_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM base_tbl; + +--DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl CASCADE; + +-- test WCO for partitions + +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger + BEFORE INSERT OR UPDATE ON child_tbl + FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +--CREATE FOREIGN TABLE foreign_tbl (a int, b int) +-- SERVER influxdb_svr OPTIONS (table 'child_tbl'); + +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION child_tbl FOR VALUES FROM (0) TO (100); + +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM child_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM child_tbl; + +--DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; + +DROP FUNCTION row_before_insupd_trigfunc; + +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +--create foreign table rem1 (f1 serial, f2 text) +-- server influxdb_svr options(table 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); +insert into loc1(f2) values('hi'); +insert into loc1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into loc1(f2) values('bye remote'); +select * from loc1; +--select * from rem1; + +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +--create foreign table grem1 ( +-- a int, +-- b int generated always as (a * 2) stored) +-- server influxdb_svr options(table 'gloc1'); +insert into gloc1 (a) values (1), (2); +update gloc1 set a = 22 where a = 2; +select * from gloc1; +--select * from grem1; + +-- =================================================================== +-- test local triggers +-- =================================================================== + +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; + +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; + +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +delete from loc1; +insert into loc1 values(1,'insert'); +update loc1 set f2 = 'update' where f1 = 1; +update loc1 set f2 = f2 || f2; + + +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_stmt_before ON loc1; +DROP TRIGGER trig_stmt_after ON loc1; + +DELETE from loc1; + + +-- Test WHEN conditions + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON loc1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Insert or update not matching: nothing happens +INSERT INTO loc1 values(1, 'insert'); +UPDATE loc1 set f2 = 'test'; + +-- Insert or update matching: triggers are fired +INSERT INTO loc1 values(2, 'update'); +UPDATE loc1 set f2 = 'update update' where f1 = '2'; + +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM loc1; + +-- cleanup +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_after_insupd ON loc1; +DROP TRIGGER trig_row_before_delete ON loc1; +DROP TRIGGER trig_row_after_delete ON loc1; + + +-- Test various RETURN statements in BEFORE triggers. + +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +-- The new values should have 'triggered' appended +INSERT INTO loc1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO loc1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE loc1 set f2 = ''; +SELECT * from loc1; +UPDATE loc1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +EXPLAIN (verbose, costs off) +UPDATE loc1 set f1 = 10; -- all columns should be transmitted +UPDATE loc1 set f1 = 10; +SELECT * from loc1; + +DELETE FROM loc1; + +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +INSERT INTO loc1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO loc1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE loc1 set f2 = ''; +SELECT * from loc1; +UPDATE loc1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_before_insupd2 ON loc1; + +DELETE from loc1; + +INSERT INTO loc1 VALUES (1, 'test'); + +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; + +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); + +-- Nothing should have changed. +INSERT INTO loc1 VALUES (2, 'test2'); + +SELECT * from loc1; + +UPDATE loc1 SET f2 = 'test2'; + +SELECT * from loc1; + +DELETE from loc1; + +SELECT * from loc1; + +DROP TRIGGER trig_null ON loc1; +DELETE from loc1; + +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO loc1(f2) VALUES ('test'); +UPDATE loc1 SET f2 = 'testo'; + +-- Test returning a system attribute +INSERT INTO loc1(f2) VALUES ('test') RETURNING ctid; + +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_local_before ON loc1; + + +-- Test direct foreign table modification functionality + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_stmt_before ON loc1; + +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_stmt_after ON loc1; + +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_row_before_insert ON loc1; + +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_row_after_insert ON loc1; + +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_row_before_update ON loc1; + +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can be pushed down +DROP TRIGGER trig_row_after_update ON loc1; + +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can't be pushed down +DROP TRIGGER trig_row_before_delete ON loc1; + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE loc1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM loc1; -- can't be pushed down +DROP TRIGGER trig_row_after_delete ON loc1; + +-- =================================================================== +-- test inheritance features +-- =================================================================== + +CREATE TABLE a (aa TEXT); +--CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +--ALTER TABLE loct SET (autovacuum_enabled = 'false'); +-- Because Influxdb does not support UPDATE, to test locally +-- we create local table. +CREATE TABLE b (bb TEXT) INHERITS (a); + + +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); + +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE b SET aa = 'new'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'newtoo'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DELETE FROM a; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DROP TABLE a CASCADE; +--DROP TABLE loct; + +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +--create table loct1 (f1 int, f2 int, f3 int); +--create table loct2 (f1 int, f2 int, f3 int); + +--alter table loct1 set (autovacuum_enabled = 'false'); +--alter table loct2 set (autovacuum_enabled = 'false'); + +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server influxdb_svr options (table 'loct1_1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server influxdb_svr options (table 'loct2_1'); + +alter table foo set (autovacuum_enabled = 'false'); +alter table bar set (autovacuum_enabled = 'false'); + +insert into foo values(1,1); +insert into foo values(3,3); +-- insert into foo2 values(2,2,2); +-- insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +-- insert into bar2 values(3,33,33); +-- insert into bar2 values(4,44,44); +-- insert into bar2 values(7,77,77); + +--explain (verbose, costs off) +--select * from ftbar where f1 in (select f1 from ftfoo) for update; +--select * from ftbar where f1 in (select f1 from ftfoo) for update; + +explain (verbose, costs off) +select * from bar2 where f1 in (select f1 from foo2) for update; +select * from bar2 where f1 in (select f1 from foo2) for update; + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; +select * from bar where f1 in (select f1 from foo) for share; + +-- Check UPDATE with inherited target and an inherited source table +-- skip, if we update to bar, bar2 also is updated because it inherits bar +-- and Influxdb fdw does not support update on bar2. +/* +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + +select tableoid::regclass, * from bar order by 1,2; + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + +select tableoid::regclass, * from bar order by 1,2; + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; +update bar set f2 = null where current of c; +rollback; + +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; +delete from foo where f1 < 5 returning *; +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; +update bar set f2 = f2 + 100 returning *; + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +explain (verbose, costs off) +update bar set f2 = f2 + 100; +update bar set f2 = f2 + 100; + +explain (verbose, costs off) +delete from bar where f2 < 400; +delete from bar where f2 < 400; + +-- cleanup +drop table foo cascade; +drop table bar cascade; +drop table loct1; +drop table loct2; + +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +--create table loct1 (a int, b text); +--create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server influxdb_svr options (table 'loct1'); +create foreign table remt2 (a int, b text) + server influxdb_svr options (table 'loct2'); +alter foreign table remt1 inherit parent; + +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); + +analyze remt1; +analyze remt2; + +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; +delete from parent using remt2 where parent.a = remt2.a returning parent; + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; +*/ +-- =================================================================== +-- test tuple routing for foreign-table partitions +-- =================================================================== +/* -- Test insert tuple routing --- create table itrtest (a int, b text) partition by list (a); --- create foreign table remp1 (a int check (a in (1)), b text) server influxdb_svr options (table 'loct1'); --- create foreign table remp2 (a int check (a in (2)), b text) server influxdb_svr options (table 'loct2'); --- alter table itrtest attach partition remp1 for values in (1); --- alter table itrtest attach partition remp2 for values in (2); +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); + +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + +select tableoid::regclass, * FROM itrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +delete from itrtest; + +create unique index loct1_idx on loct1 (a); + +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; + +select tableoid::regclass, * FROM itrtest; + +delete from itrtest; + +drop index loct1_idx; + +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); + +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; + +drop table itrtest; +drop table loct1; +drop table loct2; + +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); + +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; + +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; + +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + +drop trigger loct_br_insert_trigger on loct; + +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet --- select tableoid::regclass, * FROM itrtest; --- select tableoid::regclass, * FROM remp1; --- select tableoid::regclass, * FROM remp2; +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; +update utrtest set a = 1 returning *; + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); + +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; +update utrtest set a = 3 returning *; -- ERROR +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR + +drop table utrtest; +drop table loct; + +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); + +copy ctrtest from stdin; +1 foo +2 qux +\. + +select tableoid::regclass, * FROM ctrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +1 bar +\. + +select tableoid::regclass, * FROM remp1; + +drop table ctrtest; +drop table loct1; +drop table loct2; +*/ +-- =================================================================== +-- test COPY FROM +-- =================================================================== +/* +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; + +delete from rem2; + +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on rem2; + +delete from rem2; + +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on rem2; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; + +delete from rem2; + +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +1 foo +2 bar +\. +commit; +select * from rem3; +drop foreign table rem3; +drop table loc3; +*/ -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== @@ -985,6 +2239,62 @@ IMPORT FOREIGN SCHEMA nonesuch FROM SERVER influxdb_svr INTO import_influx2; -- IMPORT FOREIGN SCHEMA nonesuch FROM SERVER influxdb_svr INTO notthere; IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +-- CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); + +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +--IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) +-- FROM SERVER influxdb_svr INTO import_dest5; -- ERROR + +ROLLBACK; + +-- BEGIN; + + +-- CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); + +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=101']; + +-- ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); + +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=101']; + +-- SELECT count(*) +-- FROM pg_foreign_server +-- WHERE srvname = 'fetch101' +-- AND srvoptions @> array['fetch_size=202']; + +-- CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); + +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=30000']; + +-- ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); + +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=30000']; + +-- SELECT COUNT(*) +-- FROM pg_foreign_table +-- WHERE ftrelid = 'table30000'::regclass +-- AND ftoptions @> array['fetch_size=60000']; + +-- ROLLBACK; + -- =================================================================== -- test partitionwise joins -- =================================================================== @@ -1006,14 +2316,17 @@ CREATE FOREIGN TABLE ftprt2_p1 (a int, b int, c text) ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER influxdb_svr OPTIONS (table 'fprt2_p2'); +-- ANALYZE fprt2; +-- ANALYZE fprt2_p1; +-- ANALYZE fprt2_p2; -- inner join three tables EXPLAIN (COSTS OFF) SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; --- left outer join + nullable clasue -EXPLAIN (COSTS OFF) +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; @@ -1039,6 +2352,7 @@ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a RESET enable_partitionwise_join; + -- =================================================================== -- test partitionwise aggregates -- =================================================================== @@ -1050,6 +2364,11 @@ CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO ( CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER influxdb_svr OPTIONS (table 'pagg_tab_p2');; CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER influxdb_svr2 OPTIONS (table 'pagg_tab_p3');; +-- ANALYZE pagg_tab; +-- ANALYZE fpagg_tab_p1; +-- ANALYZE fpagg_tab_p2; +-- ANALYZE fpagg_tab_p3; + -- When GROUP BY clause matches with PARTITION KEY. -- Plan with partitionwise aggregates is disabled SET enable_partitionwise_aggregate TO false; diff --git a/sql/extra/join.sql b/sql/extra/join.sql index e3336f8..e916de9 100644 --- a/sql/extra/join.sql +++ b/sql/extra/join.sql @@ -42,7 +42,8 @@ CREATE FOREIGN TABLE tenk1 ( string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); -ALTER TABLE tenk1 SET WITH OIDS; +--Does not support on Postgres 12 +--ALTER TABLE tenk1 SET WITH OIDS; CREATE FOREIGN TABLE tenk2 ( unique1 int4, @@ -71,6 +72,11 @@ CREATE FOREIGN TABLE INT8_TBL( ) SERVER influxdb_svr; CREATE FOREIGN TABLE INT2_TBL(f1 int2) SERVER influxdb_svr; +-- useful in some tests below +create temp table onerow(); +insert into onerow default values; +analyze onerow; + -- -- CORRELATION NAMES -- Make sure that table/column aliases are supported @@ -90,7 +96,7 @@ SELECT '' AS "xxx", * FROM J1_TBL t1 (a, b, c); SELECT '' AS "xxx", * - FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) ORDER BY a; + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e); SELECT '' AS "xxx", t1.a, t2.e FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) @@ -323,6 +329,13 @@ NATURAL FULL JOIN (SELECT name, n as s3_n FROM t31) as s3 ) ss2; +-- Constants as join keys can also be problematic +SELECT * FROM + (SELECT name, n as s1_n FROM t11) as s1 +FULL JOIN + (SELECT name, 2 as s2_n FROM t21) as s2 +ON (s1_n = s2_n); + -- Test for propagation of nullability constraints into sub-joins @@ -484,6 +497,24 @@ CREATE FOREIGN TABLE t12 (a int, b int) SERVER influxdb_svr; CREATE FOREIGN TABLE t22 (a int, b int) SERVER influxdb_svr; CREATE FOREIGN TABLE t32 (x int, y int) SERVER influxdb_svr; +-- InfluxDB FDW does not support INSERT/DELETE/UPDATE +--INSERT INTO t1 VALUES (5, 10); +--INSERT INTO t1 VALUES (15, 20); +--INSERT INTO t1 VALUES (100, 100); +--INSERT INTO t1 VALUES (200, 1000); +--INSERT INTO t2 VALUES (200, 2000); +--INSERT INTO t3 VALUES (5, 20); +--INSERT INTO t3 VALUES (6, 7); +--INSERT INTO t3 VALUES (7, 8); +--INSERT INTO t3 VALUES (500, 100); + +--DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a; +--SELECT * FROM t3; +--DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a; +--SELECT * FROM t3; +--DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y; +--SELECT * FROM t3; + -- Test join against inheritance tree create temp table t2a () inherits (t22); @@ -737,7 +768,7 @@ LEFT JOIN ) sub2 ON sub1.key1 = sub2.key3; -test the path using join aliases, too +-- test the path using join aliases, too SELECT * FROM ( SELECT 1 as key1 ) sub1 LEFT JOIN @@ -884,8 +915,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -897,8 +928,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -924,6 +955,35 @@ select ss1.d1 from on t1.tenthous = ss1.d1 where t1.unique1 < i4.f1; +-- this variant is foldable by the remove-useless-RESULT-RTEs code + +explain (costs off) +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + -- -- test extraction of restriction OR clauses from join OR clause -- (we used to only do this for indexable clauses) @@ -1485,11 +1545,11 @@ select uunique1 from -- Take care to reference the correct RTE -- --- select atts.relid::regclass, s.* from pg_stats s join --- pg_attribute a on s.attname = a.attname and s.tablename = --- a.attrelid::regclass::text join (select unnest(indkey) attnum, --- indexrelid from pg_index i) atts on atts.attnum = a.attnum where --- schemaname != 'pg_catalog'; +--select atts.relid::regclass, s.* from pg_stats s join +-- pg_attribute a on s.attname = a.attname and s.tablename = +-- a.attrelid::regclass::text join (select unnest(indkey) attnum, +-- indexrelid from pg_index i) atts on atts.attnum = a.attnum where +-- schemaname != 'pg_catalog'; -- -- Test LATERAL @@ -1603,7 +1663,7 @@ analyze dual; select v.* from (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy); + lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); explain (verbose, costs off) select * from @@ -1629,9 +1689,9 @@ select * from int4_tbl i left join lateral (select * from int2_tbl j where i.f1 = j.f1) k on true; explain (verbose, costs off) select * from int4_tbl i left join - lateral (select coalesce(j) from int2_tbl j where i.f1 = j.f1) k on true; + lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; select * from int4_tbl i left join - lateral (select coalesce(j) from int2_tbl j where i.f1 = j.f1) k on true; + lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; explain (verbose, costs off) select * from int4_tbl a, lateral ( @@ -1687,7 +1747,15 @@ select * from select * from (select 3 as z offset 0) z where z.z = x.x ) zz on zz.z = y.y; -check handling of nested appendrels inside LATERAL +-- check dummy rels with lateral references (bug #15694) +explain (verbose, costs off) +select * from int8_tbl i8 left join lateral + (select *, i8.q2 from int4_tbl where false) ss on true; +explain (verbose, costs off) +select * from int8_tbl i8 left join lateral + (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true; + +-- check handling of nested appendrels inside LATERAL select * from ((select 2 as v) union all (select 3 as v)) as q1 cross join lateral @@ -1924,6 +1992,7 @@ CREATE FOREIGN TABLE onek ( string4 name ) SERVER influxdb_svr; +-- check that semijoin inner is not seen as unique for a portion of the outerrel explain (verbose, costs off) select t1.unique1, t2.hundred from onek t1, tenk1 t2 @@ -2431,4 +2500,4 @@ $d$; DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP SERVER influxdb_svr CASCADE; -DROP EXTENSION influxdb_fdw CASCADE; \ No newline at end of file +DROP EXTENSION influxdb_fdw CASCADE; diff --git a/sql/extra/limit.sql b/sql/extra/limit.sql index 80f74c9..98ac289 100644 --- a/sql/extra/limit.sql +++ b/sql/extra/limit.sql @@ -13,43 +13,43 @@ CREATE USER MAPPING FOR CURRENT_USER SERVER influxdb_svr OPTIONS (user 'user', p -- CREATE FOREIGN TABLE onek ( - unique1 int4, - unique2 int4, - two int4, - four int4, - ten int4, - twenty int4, - hundred int4, - thousand int4, - twothousand int4, - fivethous int4, - tenthous int4, - odd int4, - even int4, - stringu1 name, - stringu2 name, - string4 name + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name ) SERVER influxdb_svr; CREATE FOREIGN TABLE int8_tbl(q1 int8, q2 int8) SERVER influxdb_svr; CREATE FOREIGN TABLE tenk1 ( - unique1 int4, - unique2 int4, - two int4, - four int4, - ten int4, - twenty int4, - hundred int4, - thousand int4, - twothousand int4, - fivethous int4, - tenthous int4, - odd int4, - even int4, - stringu1 name, - stringu2 name, - string4 name + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); SELECT ''::text AS two, unique1, unique2, stringu1 @@ -88,7 +88,7 @@ select * from int8_tbl offset (case when random() < 0.5 then null::bigint end); -- Test assorted cases involving backwards fetch from a LIMIT plan node begin; -declare c1 cursor for select * from int8_tbl limit 10; +declare c1 scroll cursor for select * from int8_tbl limit 10; fetch all in c1; fetch 1 in c1; fetch backward 1 in c1; @@ -96,7 +96,7 @@ fetch backward all in c1; fetch backward 1 in c1; fetch all in c1; -declare c2 cursor for select * from int8_tbl limit 3; +declare c2 scroll cursor for select * from int8_tbl limit 3; fetch all in c2; fetch 1 in c2; fetch backward 1 in c2; @@ -104,7 +104,7 @@ fetch backward all in c2; fetch backward 1 in c2; fetch all in c2; -declare c3 cursor for select * from int8_tbl offset 3; +declare c3 scroll cursor for select * from int8_tbl offset 3; fetch all in c3; fetch 1 in c3; fetch backward 1 in c3; @@ -112,7 +112,7 @@ fetch backward all in c3; fetch backward 1 in c3; fetch all in c3; -declare c4 cursor for select * from int8_tbl offset 10; +declare c4 scroll cursor for select * from int8_tbl offset 10; fetch all in c4; fetch 1 in c4; fetch backward 1 in c4; @@ -184,12 +184,13 @@ select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 order by s2 desc; -- test for failure to set all aggregates' aggtranstype -explain (verbose, costs off) -select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 - from tenk1 group by thousand order by thousand limit 3; +-- influxdb does not support group by clause. +-- explain (verbose, costs off) +-- select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 +-- from tenk1 group by thousand order by thousand limit 3; -select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 - from tenk1 group by thousand order by thousand limit 3; +-- select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 +-- from tenk1 group by thousand order by thousand limit 3; -- Clean up DO $d$ @@ -205,4 +206,4 @@ $d$; DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP SERVER influxdb_svr CASCADE; -DROP EXTENSION influxdb_fdw CASCADE; \ No newline at end of file +DROP EXTENSION influxdb_fdw CASCADE; diff --git a/sql/extra/prepare.sql b/sql/extra/prepare.sql index 9b07d06..f44b85d 100644 --- a/sql/extra/prepare.sql +++ b/sql/extra/prepare.sql @@ -26,7 +26,8 @@ CREATE FOREIGN TABLE tenk1 ( string4 name ) SERVER influxdb_svr OPTIONS (table 'tenk'); -ALTER TABLE tenk1 SET WITH OIDS; +-- Does not support this command +-- ALTER TABLE tenk1 SET WITH OIDS; CREATE FOREIGN TABLE road ( name text, @@ -35,20 +36,20 @@ CREATE FOREIGN TABLE road ( SELECT name, statement, parameter_types FROM pg_prepared_statements; -PREPARE q1 AS SELECT * FROM road LIMIT 1; +PREPARE q1 AS SELECT 1 AS a; EXECUTE q1; SELECT name, statement, parameter_types FROM pg_prepared_statements; -- should fail -PREPARE q1 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q1 AS SELECT 2; -- should succeed DEALLOCATE q1; -PREPARE q1 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q1 AS SELECT 2; EXECUTE q1; -PREPARE q2 AS SELECT * FROM tenk1 LIMIT 1; +PREPARE q2 AS SELECT 2 AS b; SELECT name, statement, parameter_types FROM pg_prepared_statements; -- sql92 syntax @@ -67,24 +68,24 @@ PREPARE q2(text) AS EXECUTE q2('postgres'); -PREPARE q3(text, int, float, boolean, oid, smallint) AS +PREPARE q3(text, int, float, boolean, smallint) AS SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR - ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int) + ten = $3::bigint OR true = $4 OR odd = $5::int) ORDER BY unique1; -EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint); +EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 4::bigint); -- too few params EXECUTE q3('bool'); -- too many params -EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true); +EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 4::bigint, true); -- wrong param types -EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea'); +EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'bytea'); -- invalid type -PREPARE q4(nonexistenttype) AS SELECT * FROM road WHERE name = $1; +PREPARE q4(nonexistenttype) AS SELECT $1; -- create table as execute PREPARE q5(int, text) AS @@ -92,6 +93,9 @@ PREPARE q5(int, text) AS ORDER BY unique1; CREATE TEMPORARY TABLE q5_prep_results AS EXECUTE q5(200, 'DTAAAA'); SELECT * FROM q5_prep_results; +CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') + WITH NO DATA; +SELECT * FROM q5_prep_nodata; -- unknown or unspecified parameter types: should succeed PREPARE q6 AS @@ -109,4 +113,4 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP SERVER influxdb_svr CASCADE; -DROP EXTENSION influxdb_fdw CASCADE; \ No newline at end of file +DROP EXTENSION influxdb_fdw CASCADE; diff --git a/sql/extra/select.sql b/sql/extra/select.sql index 5c28b9a..01cab18 100644 --- a/sql/extra/select.sql +++ b/sql/extra/select.sql @@ -112,6 +112,11 @@ SELECT onek.unique1, onek.string4 FROM onek -- -- test partial btree indexes -- +-- As of 7.2, planner probably won't pick an indexscan without stats, +-- so ANALYZE first. Also, we want to prevent it from picking a bitmapscan +-- followed by sort, because that could hide index ordering problems. +-- +-- ANALYZE onek2; SET enable_seqscan TO off; SET enable_bitmapscan TO off; @@ -162,6 +167,13 @@ SELECT p.name, p.age FROM person* p; -- SELECT p.name, p.age FROM person* p ORDER BY age using >, name; +-- +-- Test some cases involving whole-row Var referencing a subquery +-- +select foo from (select 1 offset 0) as foo; +select foo from (select null offset 0) as foo; +select foo from (select 'xyzzy',1,null offset 0) as foo; + -- -- Test VALUES lists -- @@ -182,7 +194,7 @@ select * from onek order by unique1; -- VALUES is also legal as a standalone query or a set-operation member --- VALUES (1,2), (3,4+4), (7,77.7); +VALUES (1,2), (3,4+4), (7,77.7); VALUES (1,2), (3,4+4), (7,77.7) UNION ALL @@ -198,9 +210,9 @@ CREATE FOREIGN TABLE foo (f1 int) SERVER influxdb_svr; SELECT * FROM foo ORDER BY f1; SELECT * FROM foo ORDER BY f1 ASC; -- same thing --- SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 NULLS FIRST; SELECT * FROM foo ORDER BY f1 DESC; --- SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; -- check if indexscans do the right things -- CREATE INDEX fooi ON foo (f1); @@ -279,31 +291,30 @@ select unique1, unique2 from onek2 -- -- ORDER BY on a constant doesn't really need any sorting --- SELECT 1 AS x ORDER BY x; +SELECT 1 AS x ORDER BY x; -- But ORDER BY on a set-valued expression does --- create function sillysrf(int) returns setof int as --- 'values (1),(10),(2),($1)' language sql immutable; +create function sillysrf(int) returns setof int as + 'values (1),(10),(2),($1)' language sql immutable; --- select sillysrf(42); --- select sillysrf(-1) order by 1; +select sillysrf(42); +select sillysrf(-1) order by 1; --- drop function sillysrf(int); +drop function sillysrf(int); -- X = X isn't a no-op, it's effectively X IS NOT NULL assuming = is strict -- (see bug #5084) --- select * from (values (2),(null),(1)) v(k) where k = k order by k; --- select * from (values (2),(null),(1)) v(k) where k = k; +select * from (values (2),(null),(1)) v(k) where k = k order by k; +select * from (values (2),(null),(1)) v(k) where k = k; -- Test partitioned tables with no partitions, which should be handled the -- same as the non-inheritance case when expanding its RTE. --- create table list_parted_tbl (a int,b int) partition by list (a); --- create table list_parted_tbl1 partition of list_parted_tbl --- for values in (1) partition by list(b); --- create foreign table list_parted_tbl (a int,b int) server influxdb_svr; --- explain (costs off) select * from list_parted_tbl; --- drop table list_parted_tbl; +create table list_parted_tbl (a int,b int) partition by list (a); +create table list_parted_tbl1 partition of list_parted_tbl + for values in (1) partition by list(b); +explain (costs off) select * from list_parted_tbl; +drop table list_parted_tbl; DROP USER MAPPING FOR CURRENT_USER SERVER influxdb_svr; DROP SERVER influxdb_svr CASCADE; -DROP EXTENSION influxdb_fdw CASCADE; \ No newline at end of file +DROP EXTENSION influxdb_fdw CASCADE; diff --git a/test.sh b/test.sh index ebba823..099e424 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1,4 @@ -export USE_PGXS=1 -./init.sh && make clean && make && make install && make installcheck \ No newline at end of file +#export USE_PGXS=1 +sed -i 's/REGRESS =.*/REGRESS = aggregate influxdb_fdw/' Makefile + +./init.sh && make clean && make && make install && make check diff --git a/test_extra.sh b/test_extra.sh old mode 100644 new mode 100755