CREATE SCHEMA values_subquery; SET search_path TO values_subquery; CREATE TABLE test_values (key int, value text, data jsonb); SELECT create_distributed_table('test_values', 'key'); create_distributed_table --------------------------------------------------------------------- (1 row) INSERT INTO test_values SELECT i, i::text, ('{"value":"' || i::text || '"}')::jsonb FROM generate_series(0,100)i; CREATE TABLE test_values_ref (key int); SELECT create_reference_table('test_values_ref'); create_reference_table --------------------------------------------------------------------- (1 row) INSERT INTO test_values_ref SELECT i FROM generate_series(0,100)i; -- the aim of this test is to show when Citus can pushdown -- VALUES and when it cannot. With DEBUG1, we can see the -- recursive planning, so we can detect the pushdown SET client_min_messages TO DEBUG1; -- values in WHERE clause WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT num FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) -- values in WHERE clause with DISTINCT WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT DISTINCT num FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) -- we can control the materialization threshold via GUC -- we set it 2, and the query has 3 tuples, so the planner -- decides to materialize the VALUES clause BEGIN; SET LOCAL citus.values_materialization_threshold TO 2; WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT DISTINCT num FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT column1 AS num, column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*" DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM values_subquery.test_values WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT DISTINCT cte_1.num FROM (SELECT intermediate_result.num, intermediate_result.letter FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(num integer, letter text)) cte_1)) count --------------------------------------------------------------------- 3 (1 row) COMMIT; -- we can control the materialization threshold via GUC -- we set it -1, and the query is never materialized -- decides to materialize the VALUES clause BEGIN; SET LOCAL citus.values_materialization_threshold TO -1; WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT DISTINCT num FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) COMMIT; -- values with repeat can be pushed down WITH cte_1 (letter) AS (VALUES (repeat('1',10))) SELECT count(*) FROM test_values WHERE value IN (SELECT DISTINCT letter FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 0 (1 row) -- values in WHERE clause with DISTINCT, and CTE defined in subquery SELECT count(*) FROM test_values WHERE key IN (WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT DISTINCT num FROM cte_1); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) -- values in WHERE clause within a subquery WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2'), (3, '3')) SELECT count(*) FROM test_values WHERE key IN (SELECT key FROM test_values WHERE value NOT IN (SELECT letter FROM cte_1) GROUP BY key); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 98 (1 row) -- VALUES nested multiple CTEs WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2'), (3, '3')), cte_2 (num, letter) AS (SELECT * FROM cte_1) SELECT count(DISTINCT key) FROM test_values WHERE key >ANY(SELECT num FROM cte_2); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: CTE cte_2 is going to be inlined via distributed planning count --------------------------------------------------------------------- 99 (1 row) -- values with set operations can be pushed down as long as -- they are JOINed with a distributed table SELECT count(*) FROM ( (WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2')) SELECT key FROM test_values WHERE key >ANY(SELECT num FROM cte_1)) UNION (WITH cte_1 (num,letter) AS (VALUES (2, '2'), (3, '3')) SELECT key FROM test_values WHERE key >ANY(SELECT num FROM cte_1)) ) as foo; DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 99 (1 row) -- values with set operations can be pushed down as long as -- they are JOINed with a distributed table SELECT count(*) FROM ( (WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2')) SELECT key FROM test_values WHERE key >ANY(SELECT num FROM cte_1)) UNION ALL (WITH cte_1 (num,letter) AS (VALUES (2, '2'), (3, '3')) SELECT key FROM test_values WHERE key >ANY(SELECT num FROM cte_1)) ) as foo GROUP BY key ORDER BY 1 DESC LIMIT 3; DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: push down of limit count: 3 count --------------------------------------------------------------------- 2 2 2 (3 rows) -- values with set operations cannot be pushed along with -- distributed tables SELECT count(*) FROM ( (WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2')) SELECT num FROM cte_1) UNION (SELECT key FROM test_values) ) as foo; DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT key FROM values_subquery.test_values DEBUG: generating subplan XXX_2 for subquery SELECT cte_1.num FROM (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'1'::text), (2,'2'::text)) "*VALUES*") cte_1 UNION SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.num FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(num integer)) foo count --------------------------------------------------------------------- 101 (1 row) -- values with set operations cannot be pushed along with -- distributed tables SELECT count(*) FROM ( (WITH cte_1 (num,letter) AS (VALUES (1, '1'), (2, '2')) SELECT num FROM cte_1) UNION ALL (SELECT key FROM test_values) ) as foo; DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT key FROM values_subquery.test_values DEBUG: generating subplan XXX_2 for subquery SELECT cte_1.num FROM (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'1'::text), (2,'2'::text)) "*VALUES*") cte_1 UNION ALL SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.num FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(num integer)) foo count --------------------------------------------------------------------- 103 (1 row) -- values in WHERE clause with a subquery can be pushed down SELECT count(*) FROM test_values WHERE key IN (SELECT num FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(num, v)); count --------------------------------------------------------------------- 3 (1 row) -- values with INNER JOIN SELECT count(*) FROM test_values JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b) ) as foo (num,letter) ON (key = num); count --------------------------------------------------------------------- 3 (1 row) -- values with supported OUTER JOIN SELECT count(*) FROM test_values LEFT JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b) ) as foo (num,letter) ON (key = num); count --------------------------------------------------------------------- 101 (1 row) -- VALUES with supported OUTER join (since test_values is recursively planned) SELECT count(*) FROM test_values RIGHT JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b) ) as foo (num,letter) ON (key = num); DEBUG: recursively planning left side of the right join since the outer side is a recurring rel DEBUG: recursively planning distributed relation "test_values" since it is part of a distributed join node that is outer joined with a recurring rel DEBUG: Wrapping relation "test_values" to a subquery DEBUG: generating subplan XXX_1 for subquery SELECT key FROM values_subquery.test_values WHERE true DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT test_values_1.key, NULL::text AS value, NULL::jsonb AS data FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) test_values_1) test_values RIGHT JOIN (SELECT t.a, t.b FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) t(a, b)) foo(num, letter) ON ((test_values.key OPERATOR(pg_catalog.=) foo.num))) count --------------------------------------------------------------------- 3 (1 row) -- values with router queries SELECT count(*) FROM test_values LEFT JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b) ) as foo (num,letter) ON (key = num) WHERE key = 1; count --------------------------------------------------------------------- 1 (1 row) -- values with reference tables SELECT count(*) FROM test_values_ref LEFT JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b) ) as foo (num,letter) ON (key = num); count --------------------------------------------------------------------- 101 (1 row) -- values with non-coloated subquery join -- VALUES can still be pushed down, the recursive planning -- happens for non-colocated join between tables SELECT count(*) FROM test_values WHERE key NOT IN (WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT key FROM test_values WHERE value NOT IN (SELECT letter FROM cte_1)); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT key FROM values_subquery.test_values WHERE (NOT (value OPERATOR(pg_catalog.=) ANY (SELECT cte_1.letter FROM (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*") cte_1))) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM values_subquery.test_values WHERE (NOT (key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)))) count --------------------------------------------------------------------- 0 (1 row) -- values can be recursively planned if merge step is required WITH cte_1 (num,letter) AS NOT MATERIALIZED (VALUES (1, 'one'), (2, 'two'), (3, 'three')), cte_2 (num,letter) AS NOT MATERIALIZED (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT count(DISTINCT num) FROM cte_1) AND key IN (SELECT num FROM cte_2 ORDER BY letter LIMIT 1) AND key IN (SELECT max(num) FROM cte_1 JOIN cte_2 USING (num)); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: CTE cte_2 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT count(DISTINCT num) AS count FROM (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*") cte_1 DEBUG: generating subplan XXX_2 for subquery SELECT num FROM (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*") cte_2 ORDER BY letter LIMIT 1 DEBUG: generating subplan XXX_3 for subquery SELECT max(cte_1.num) AS max FROM ((SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*") cte_1 JOIN (SELECT "*VALUES*".column1 AS num, "*VALUES*".column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*") cte_2 USING (num)) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM values_subquery.test_values WHERE ((key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.count FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(count bigint))) AND (key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.num FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(num integer))) AND (key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.max FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(max integer)))) count --------------------------------------------------------------------- 0 (1 row) -- some more complex joins -- in theory we can pushdown the VALUES here as well -- but to behave consistently with other recurring tuples -- we prefer recursive planning SELECT count(*) as subquery_count FROM ( SELECT key FROM test_values WHERE (value = '5' OR value = '13') GROUP BY key HAVING count(distinct value) < 2) as a LEFT JOIN ( SELECT (SELECT a FROM (VALUES (1, 'one')) as t(a,b)) ) AS foo (num) ON a.key = foo.num WHERE foo.num IS NULL GROUP BY a.key; DEBUG: generating subplan XXX_1 for subquery SELECT (SELECT t.a FROM (VALUES (1,'one'::text)) t(a, b)) AS a DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS subquery_count FROM ((SELECT test_values.key FROM values_subquery.test_values WHERE ((test_values.value OPERATOR(pg_catalog.=) '5'::text) OR (test_values.value OPERATOR(pg_catalog.=) '13'::text)) GROUP BY test_values.key HAVING (count(DISTINCT test_values.value) OPERATOR(pg_catalog.<) 2)) a LEFT JOIN (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) foo(num) ON ((a.key OPERATOR(pg_catalog.=) foo.num))) WHERE (foo.num IS NULL) GROUP BY a.key subquery_count --------------------------------------------------------------------- 1 1 (2 rows) -- only immutable functions can be pushed down WITH cte_1 (num,letter) AS (VALUES (random(), 'one'), (2, 'two'), (3, 'three')) SELECT count(*) > 0 FROM test_values WHERE key IN (SELECT num FROM cte_1); DEBUG: generating subplan XXX_1 for CTE cte_1: VALUES (random(),'one'::text), (2,'two'::text), (3,'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (count(*) OPERATOR(pg_catalog.>) 0) FROM values_subquery.test_values WHERE ((key)::double precision OPERATOR(pg_catalog.=) ANY (SELECT cte_1.num FROM (SELECT intermediate_result.column1 AS num, intermediate_result.column2 AS letter FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 double precision, column2 text)) cte_1)) ?column? --------------------------------------------------------------------- t (1 row) -- only immutable functions can be pushed down SELECT count(*) FROM test_values WHERE key IN (SELECT num FROM (VALUES (random(), 'one'), (2, 'two'), (3, 'three')) as t(num, v)); DEBUG: generating subplan XXX_1 for subquery VALUES (random(),'one'::text), (2,'two'::text), (3,'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM values_subquery.test_values WHERE ((key)::double precision OPERATOR(pg_catalog.=) ANY (SELECT t.num FROM (SELECT intermediate_result.column1, intermediate_result.column2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 double precision, column2 text)) t(num, v))) count --------------------------------------------------------------------- 2 (1 row) -- only immutable functions can be pushed down SELECT count(*) FROM test_values JOIN (SELECT a,b FROM (VALUES (1, 'one'), (2, 'two'), (random(), 'three')) as t(a,b) ) as foo (num,letter) ON (key = num); DEBUG: generating subplan XXX_1 for subquery VALUES (1,'one'::text), (2,'two'::text), (random(),'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (values_subquery.test_values JOIN (SELECT t.a, t.b FROM (SELECT intermediate_result.column1, intermediate_result.column2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 double precision, column2 text)) t(a, b)) foo(num, letter) ON (((test_values.key)::double precision OPERATOR(pg_catalog.=) foo.num))) count --------------------------------------------------------------------- 2 (1 row) -- materialized CTEs are recursively planned always WITH cte_1 (num,letter) AS MATERIALIZED (VALUES (1, 'one'), (2, 'two'), (3, 'three')) SELECT count(*) > 0 FROM test_values WHERE key IN (SELECT num FROM cte_1); DEBUG: generating subplan XXX_1 for CTE cte_1: VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (count(*) OPERATOR(pg_catalog.>) 0) FROM values_subquery.test_values WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT cte_1.num FROM (SELECT intermediate_result.column1 AS num, intermediate_result.column2 AS letter FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 integer, column2 text)) cte_1)) ?column? --------------------------------------------------------------------- t (1 row) -- because the FROM clause recurs, the subquery in WHERE -- clause is recursively planned SELECT num FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(num, v) WHERE num > (SELECT max(key) FROM test_values); DEBUG: generating subplan XXX_1 for subquery SELECT max(key) AS max FROM values_subquery.test_values DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT num FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) t(num, v) WHERE (num OPERATOR(pg_catalog.>) (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max integer))) num --------------------------------------------------------------------- (0 rows) -- but, we cannot recursively plan if the subquery that VALUEs is correlated SELECT * FROM test_values as t1 JOIN LATERAL ( SELECT t1.key FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(num, v) WHERE num > (SELECT max(key) FROM test_values)) as foo ON (true); DEBUG: generating subplan XXX_1 for subquery SELECT max(key) AS max FROM values_subquery.test_values DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT t1.key, t1.value, t1.data, foo.key FROM (values_subquery.test_values t1 JOIN LATERAL (SELECT t1.key FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) t(num, v) WHERE (t.num OPERATOR(pg_catalog.>) (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max integer)))) foo ON (true)) ERROR: correlated subqueries are not supported when the FROM clause contains VALUES -- VALUES can be the inner relationship in a join SELECT count(*) FROM (SELECT random() FROM test_values JOIN (SELECT a, b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b)) as values_data(a,b) ON test_values.key > values_data.a) subquery_1; count --------------------------------------------------------------------- 294 (1 row) -- VALUES can be the left relationship in a join SELECT count(*) FROM (SELECT random() FROM test_values LEFT JOIN (SELECT a, b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b)) as values_data(a,b) ON test_values.key > values_data.a) subquery_1; count --------------------------------------------------------------------- 296 (1 row) -- VALUES can be the right relationship in a join SELECT count(*) FROM (SELECT random() FROM test_values RIGHT JOIN (SELECT a, b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b)) as values_data(a,b) ON test_values.key > values_data.a) subquery_1; DEBUG: recursively planning left side of the right join since the outer side is a recurring rel DEBUG: recursively planning distributed relation "test_values" since it is part of a distributed join node that is outer joined with a recurring rel DEBUG: Wrapping relation "test_values" to a subquery DEBUG: generating subplan XXX_1 for subquery SELECT key FROM values_subquery.test_values WHERE true DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT random() AS random FROM ((SELECT test_values_1.key, NULL::text AS value, NULL::jsonb AS data FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) test_values_1) test_values RIGHT JOIN (SELECT t.a, t.b FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) t(a, b)) values_data(a, b) ON ((test_values.key OPERATOR(pg_catalog.>) values_data.a)))) subquery_1 count --------------------------------------------------------------------- 294 (1 row) -- subquery IN WHERE clause need to be recursively planned -- but it is correlated so cannot be pushed down SELECT count(*) FROM (SELECT a, b FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(a,b)) as values_data(a,b) WHERE NOT EXISTS (SELECT value FROM test_values WHERE test_values.key = values_data.a ); ERROR: correlated subqueries are not supported when the FROM clause contains VALUES -- we can pushdown as long as GROUP BY on dist key SELECT count(*) FROM test_values WHERE key IN ( SELECT a FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as values_data(a,b) ) GROUP BY key ORDER BY 1 DESC LIMIT 3; DEBUG: push down of limit count: 3 count --------------------------------------------------------------------- 1 1 1 (3 rows) -- CTEs are not inlined for modification queries -- so always recursively planned WITH cte_1 (num,letter) AS (VALUES (1, 'one'), (2, 'two'), (3, 'three')) UPDATE test_values SET value = '1' WHERE key IN (SELECT num FROM cte_1); DEBUG: generating subplan XXX_1 for CTE cte_1: VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE values_subquery.test_values SET value = '1'::text WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT cte_1.num FROM (SELECT intermediate_result.column1 AS num, intermediate_result.column2 AS letter FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 integer, column2 text)) cte_1)) -- we can pushdown modification queries with VALUEs UPDATE test_values SET value = '1' WHERE key IN (SELECT num FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) as t(num, v)); -- we can pushdown modification queries with VALUEs as long as they contain immutable functions UPDATE test_values SET value = '1' WHERE key IN (SELECT num FROM (VALUES (random(), 'one'), (2, 'two'), (3, 'three')) as t(num, v)); DEBUG: generating subplan XXX_1 for subquery VALUES (random(),'one'::text), (2,'two'::text), (3,'three'::text) DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE values_subquery.test_values SET value = '1'::text WHERE ((key)::double precision OPERATOR(pg_catalog.=) ANY (SELECT t.num FROM (SELECT intermediate_result.column1, intermediate_result.column2 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(column1 double precision, column2 text)) t(num, v))) -- prepared statements should be fine to pushdown PREPARE test_values_pushdown(int, int,int) AS WITH cte_1 (num,letter) AS (VALUES ($1, 'one'), ($2, 'two'), ($3, 'three')) SELECT count(*) FROM test_values WHERE key IN (SELECT num FROM cte_1); EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) -- prepared statements with volatile functions should be still pushed down -- because the function is evaluated on the coordinator CREATE OR REPLACE FUNCTION fixed_volatile_value() RETURNS integer VOLATILE AS $$ BEGIN RAISE NOTICE 'evaluated on the coordinator'; RETURN 1; END; $$ LANGUAGE plpgsql; DEBUG: switching to sequential query execution mode DETAIL: A command for a distributed function is run. To make sure subsequent commands see the function correctly we need to make sure to use only one connection for all future commands EXECUTE test_values_pushdown(fixed_volatile_value(),2,3); NOTICE: evaluated on the coordinator CONTEXT: PL/pgSQL function fixed_volatile_value() line XX at RAISE DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) -- threshold should trigger materialization of VALUES in the first -- statement and pushdown in the second as -1 disables materialization BEGIN; SET LOCAL citus.values_materialization_threshold TO 0; EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning DEBUG: generating subplan XXX_1 for subquery SELECT column1 AS num, column2 AS letter FROM (VALUES (1,'one'::text), (2,'two'::text), (3,'three'::text)) "*VALUES*" DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM values_subquery.test_values WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT cte_1.num FROM (SELECT intermediate_result.num, intermediate_result.letter FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(num integer, letter text)) cte_1)) count --------------------------------------------------------------------- 3 (1 row) SET LOCAL citus.values_materialization_threshold TO -1; EXECUTE test_values_pushdown(1,2,3); DEBUG: CTE cte_1 is going to be inlined via distributed planning count --------------------------------------------------------------------- 3 (1 row) COMMIT; SET client_min_messages TO WARNING; DROP SCHEMA values_subquery CASCADE;