-- Tests for modifying CTEs and CTEs in modifications
SET citus.next_shard_id TO 1502000;
CREATE SCHEMA with_modifying;
SET search_path TO with_modifying, public;
CREATE TABLE with_modifying.modify_table (id int, val int);
SELECT create_distributed_table('modify_table', 'id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

CREATE TABLE with_modifying.users_table (LIKE public.users_table INCLUDING ALL);
SELECT create_distributed_table('with_modifying.users_table', 'user_id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

INSERT INTO with_modifying.users_table SELECT * FROM public.users_table;
CREATE TABLE with_modifying.summary_table (id int, counter int);
SELECT create_distributed_table('summary_table', 'id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

CREATE TABLE with_modifying.anchor_table (id int);
SELECT create_reference_table('anchor_table');
 create_reference_table
---------------------------------------------------------------------

(1 row)

-- basic insert query in CTE
WITH basic_insert AS (
	INSERT INTO users_table VALUES (1), (2), (3) RETURNING *
)
SELECT
	*
FROM
	basic_insert
ORDER BY
	user_id;
 user_id | time | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       1 |      |         |         |         |
       2 |      |         |         |         |
       3 |      |         |         |         |
(3 rows)

-- single-shard UPDATE in CTE
WITH basic_update AS (
	UPDATE users_table SET value_3=41 WHERE user_id=1 RETURNING *
)
SELECT
	*
FROM
	basic_update
ORDER BY
	user_id,
	time
LIMIT 10;
 user_id |              time               | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       1 | Wed Nov 22 22:51:43.132261 2017 |       4 |       0 |      41 |
       1 | Thu Nov 23 03:32:50.803031 2017 |       3 |       2 |      41 |
       1 | Thu Nov 23 09:26:42.145043 2017 |       1 |       3 |      41 |
       1 | Thu Nov 23 11:11:24.40789 2017  |       3 |       4 |      41 |
       1 | Thu Nov 23 11:44:57.515981 2017 |       4 |       3 |      41 |
       1 | Thu Nov 23 17:23:03.441394 2017 |       5 |       4 |      41 |
       1 | Thu Nov 23 17:30:34.635085 2017 |       3 |       4 |      41 |
       1 |                                 |         |         |      41 |
       1 |                                 |         |         |      41 |
(9 rows)

-- multi-shard UPDATE in CTE
WITH basic_update AS (
	UPDATE users_table SET value_3=42 WHERE value_2=1 RETURNING *
)
SELECT
	*
FROM
	basic_update
ORDER BY
	user_id,
	time
LIMIT 10;
 user_id |              time               | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       2 | Thu Nov 23 06:50:30.797805 2017 |       1 |       1 |      42 |
       2 | Thu Nov 23 06:56:38.46819 2017  |       0 |       1 |      42 |
       2 | Thu Nov 23 13:52:54.83829 2017  |       3 |       1 |      42 |
       3 | Wed Nov 22 18:43:51.450263 2017 |       1 |       1 |      42 |
       3 | Thu Nov 23 00:15:45.610845 2017 |       1 |       1 |      42 |
       4 | Wed Nov 22 23:59:46.493416 2017 |       3 |       1 |      42 |
       4 | Thu Nov 23 01:55:21.824618 2017 |       3 |       1 |      42 |
       4 | Thu Nov 23 06:50:08.101207 2017 |       2 |       1 |      42 |
       4 | Thu Nov 23 07:09:37.382372 2017 |       4 |       1 |      42 |
       4 | Thu Nov 23 08:38:45.877401 2017 |       4 |       1 |      42 |
(10 rows)

-- single-shard DELETE in CTE
WITH basic_delete AS (
	DELETE FROM users_table WHERE user_id=6 RETURNING *
)
SELECT
	*
FROM
	basic_delete
ORDER BY
	user_id,
	time
LIMIT 10;
 user_id |              time               | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       6 | Wed Nov 22 20:15:53.317797 2017 |       1 |       1 |      42 |
       6 | Wed Nov 22 23:01:24.82289 2017  |       2 |       4 |       1 |
       6 | Thu Nov 23 00:07:11.068353 2017 |       1 |       1 |      42 |
       6 | Thu Nov 23 00:09:44.19812 2017  |       5 |       2 |       0 |
       6 | Thu Nov 23 01:13:50.526322 2017 |       2 |       4 |       1 |
       6 | Thu Nov 23 01:14:55.769581 2017 |       0 |       0 |       5 |
       6 | Thu Nov 23 10:22:11.02918 2017  |       5 |       0 |       5 |
       6 | Thu Nov 23 11:08:04.244582 2017 |       2 |       3 |       2 |
       6 | Thu Nov 23 13:51:16.92838 2017  |       0 |       4 |       2 |
       6 | Thu Nov 23 14:43:18.024104 2017 |       3 |       2 |       5 |
(10 rows)

-- multi-shard DELETE in CTE
WITH basic_delete AS (
	DELETE FROM users_table WHERE value_3=41 RETURNING *
)
SELECT
	*
FROM
	basic_delete
ORDER BY
	user_id,
	time
LIMIT 10;
 user_id |              time               | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       1 | Wed Nov 22 22:51:43.132261 2017 |       4 |       0 |      41 |
       1 | Thu Nov 23 03:32:50.803031 2017 |       3 |       2 |      41 |
       1 | Thu Nov 23 09:26:42.145043 2017 |       1 |       3 |      41 |
       1 | Thu Nov 23 11:11:24.40789 2017  |       3 |       4 |      41 |
       1 | Thu Nov 23 11:44:57.515981 2017 |       4 |       3 |      41 |
       1 | Thu Nov 23 17:23:03.441394 2017 |       5 |       4 |      41 |
       1 | Thu Nov 23 17:30:34.635085 2017 |       3 |       4 |      41 |
       1 |                                 |         |         |      41 |
       1 |                                 |         |         |      41 |
(9 rows)

-- INSERT...SELECT query in CTE
WITH copy_table AS (
	INSERT INTO users_table SELECT * FROM users_table WHERE user_id = 0 OR user_id = 3 RETURNING *
)
SELECT
	*
FROM
	copy_table
ORDER BY
	user_id,
	time
LIMIT 10;
 user_id |              time               | value_1 | value_2 | value_3 | value_4
---------------------------------------------------------------------
       3 | Wed Nov 22 18:43:51.450263 2017 |       1 |       1 |      42 |
       3 | Wed Nov 22 20:43:31.008625 2017 |       1 |       3 |       2 |
       3 | Wed Nov 22 23:24:32.080584 2017 |       3 |       2 |       5 |
       3 | Thu Nov 23 00:15:45.610845 2017 |       1 |       1 |      42 |
       3 | Thu Nov 23 03:23:24.702501 2017 |       1 |       2 |       5 |
       3 | Thu Nov 23 03:52:32.008895 2017 |       4 |       2 |       0 |
       3 | Thu Nov 23 04:01:08.04806 2017  |       5 |       5 |       3 |
       3 | Thu Nov 23 05:01:44.885505 2017 |       3 |       5 |       4 |
       3 | Thu Nov 23 06:20:05.854857 2017 |       1 |       4 |       2 |
       3 | Thu Nov 23 09:57:41.540228 2017 |       2 |       2 |       3 |
(10 rows)

-- CTEs prior to INSERT...SELECT via the coordinator should work
WITH cte AS (
	SELECT user_id FROM users_table WHERE value_2 IN (1, 2)
)
INSERT INTO modify_table (SELECT * FROM cte);
WITH cte_1 AS (
	SELECT user_id, value_2 FROM users_table WHERE value_2 IN (1, 2, 3, 4)
),
cte_2 AS (
	SELECT user_id, value_2 FROM users_table WHERE value_2 IN (3, 4, 5, 6)
)
INSERT INTO modify_table (SELECT cte_1.user_id FROM cte_1 join cte_2 on cte_1.value_2=cte_2.value_2);
-- we execute the query within a function to consolidate the error messages
-- between different executors
CREATE FUNCTION raise_failed_execution_cte(query text) RETURNS void AS $$
BEGIN
        EXECUTE query;
        EXCEPTION WHEN OTHERS THEN
        IF SQLERRM LIKE '%more than one row returned by a subquery used as an expression%' THEN
                RAISE 'Task failed to execute';
        ELSIF SQLERRM LIKE '%could not receive query results%' THEN
          RAISE 'Task failed to execute';
        END IF;
END;
$$LANGUAGE plpgsql;
SET client_min_messages TO ERROR;
\set VERBOSITY terse
-- even if this is an INSERT...SELECT, the CTE is under SELECT
-- function joins in CTE results can create lateral joins that are not supported
SELECT raise_failed_execution_cte($$
	WITH cte AS (
		SELECT user_id, value_2 FROM users_table WHERE value_2 IN (1, 2)
	)
	INSERT INTO modify_table (SELECT (SELECT value_2 FROM cte GROUP BY value_2));
$$);
ERROR:  Task failed to execute
SET client_min_messages TO DEFAULT;
\set VERBOSITY DEFAULT
-- CTEs prior to any other modification should error out
WITH cte AS (
	SELECT value_2 FROM users_table WHERE user_id IN (1, 2, 3)
)
DELETE FROM modify_table WHERE id IN (SELECT value_2 FROM cte);
WITH cte AS (
	SELECT value_2 FROM users_table WHERE user_id IN (1, 2, 3)
)
UPDATE modify_table SET val=-1 WHERE val IN (SELECT * FROM cte);
WITH user_data AS (
	SELECT user_id, value_2 FROM users_table
)
INSERT INTO modify_table SELECT * FROM user_data;
WITH raw_data AS (
	DELETE FROM modify_table RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
SELECT * FROM summary_table ORDER BY id;
 id | counter
---------------------------------------------------------------------
  2 |      20
  3 |      38
  4 |      24
  5 |      27
(4 rows)

SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
     0
(1 row)

INSERT INTO modify_table VALUES (1,1), (2, 2), (3,3);
WITH raw_data AS (
	DELETE FROM modify_table RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       1
  2 |       1
  2 |      20
  3 |       1
  3 |      38
  4 |      24
  5 |      27
(7 rows)

SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
     0
(1 row)

WITH insert_reference AS (
	INSERT INTO anchor_table VALUES (1), (2) RETURNING *
)
SELECT id FROM insert_reference ORDER BY id;
 id
---------------------------------------------------------------------
  1
  2
(2 rows)

WITH anchor_data AS (
	SELECT * FROM anchor_table
),
raw_data AS (
	DELETE FROM modify_table RETURNING *
),
summary_data AS (
	DELETE FROM summary_table RETURNING *
)
INSERT INTO
	summary_table
SELECT id, SUM(counter) FROM (
	(SELECT raw_data.id, COUNT(*) AS counter FROM raw_data, anchor_data
		WHERE raw_data.id = anchor_data.id GROUP BY raw_data.id)
	UNION ALL
	(SELECT * FROM summary_data)) AS all_rows
GROUP BY
	id;
SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
     0
(1 row)

SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       1
  2 |      21
  3 |      39
  4 |      24
  5 |      27
(5 rows)

WITH added_data AS (
	INSERT INTO modify_table VALUES (1,2), (1,6), (2,4), (3,6) RETURNING *
),
raw_data AS (
	DELETE FROM modify_table WHERE id = 1 AND val = (SELECT MAX(val) FROM added_data) RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
     3
(1 row)

SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       1
  1 |       1
  2 |      21
  3 |      39
  4 |      24
  5 |      27
(6 rows)

-- Merge rows in the summary_table
WITH summary_data AS (
	DELETE FROM summary_table RETURNING *
)
INSERT INTO summary_table SELECT id, SUM(counter) AS counter FROM summary_data GROUP BY id;
SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       2
  2 |      21
  3 |      39
  4 |      24
  5 |      27
(5 rows)

SELECT * FROM modify_table ORDER BY id, val;
 id | val
---------------------------------------------------------------------
  1 |   2
  2 |   4
  3 |   6
(3 rows)

SELECT * FROM anchor_table ORDER BY id;
 id
---------------------------------------------------------------------
  1
  2
(2 rows)

INSERT INTO modify_table VALUES (11, 1), (12, 2), (13, 3);
WITH select_data AS (
	SELECT * FROM modify_table
),
raw_data AS (
	DELETE FROM modify_table WHERE id >= (SELECT min(id) FROM select_data WHERE id > 10) RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
INSERT INTO modify_table VALUES (21, 1), (22, 2), (23, 3);
-- read ids from the same table
WITH distinct_ids AS (
  SELECT DISTINCT id FROM modify_table
),
update_data AS (
  UPDATE modify_table SET val = 100 WHERE id > 10 AND
  	id IN (SELECT * FROM distinct_ids) RETURNING *
)
SELECT count(*) FROM update_data;
 count
---------------------------------------------------------------------
     3
(1 row)

-- read ids from a different table
WITH distinct_ids AS (
  SELECT DISTINCT id FROM summary_table
),
update_data AS (
  UPDATE modify_table SET val = 100 WHERE id > 10 AND
  	id IN (SELECT * FROM distinct_ids) RETURNING *
)
SELECT count(*) FROM update_data;
 count
---------------------------------------------------------------------
     0
(1 row)

-- test update with generate series
UPDATE modify_table SET val = 200 WHERE id > 10 AND
	id IN (SELECT 2*s FROM generate_series(1,20) s);
-- test update with generate series in CTE
WITH update_data AS (
	UPDATE modify_table SET val = 300 WHERE id > 10 AND
	id IN (SELECT 3*s FROM generate_series(1,20) s) RETURNING *
)
SELECT COUNT(*) FROM update_data;
 count
---------------------------------------------------------------------
     1
(1 row)

WITH delete_rows AS (
	DELETE FROM modify_table WHERE id > 10 RETURNING *
)
SELECT * FROM delete_rows ORDER BY id, val;
 id | val
---------------------------------------------------------------------
 21 | 300
 22 | 200
 23 | 100
(3 rows)

WITH delete_rows AS (
	DELETE FROM summary_table WHERE id > 10 RETURNING *
)
SELECT * FROM delete_rows ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
 11 |       1
 12 |       1
 13 |       1
(3 rows)

-- Check modifiying CTEs inside a transaction
BEGIN;
WITH raw_data AS (
	DELETE FROM modify_table RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
WITH insert_reference AS (
	INSERT INTO anchor_table VALUES (3), (4) RETURNING *
)
SELECT id FROM insert_reference ORDER BY id;
 id
---------------------------------------------------------------------
  3
  4
(2 rows)

SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       1
  1 |       2
  2 |       1
  2 |      21
  3 |       1
  3 |      39
  4 |      24
  5 |      27
(8 rows)

SELECT * FROM modify_table ORDER BY id, val;
 id | val
---------------------------------------------------------------------
(0 rows)

SELECT * FROM anchor_table ORDER BY id;
 id
---------------------------------------------------------------------
  1
  2
  3
  4
(4 rows)

ROLLBACK;
SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       2
  2 |      21
  3 |      39
  4 |      24
  5 |      27
(5 rows)

SELECT * FROM modify_table ORDER BY id, val;
 id | val
---------------------------------------------------------------------
  1 |   2
  2 |   4
  3 |   6
(3 rows)

SELECT * FROM anchor_table ORDER BY id;
 id
---------------------------------------------------------------------
  1
  2
(2 rows)

-- Test delete with subqueries
WITH deleted_rows AS (
	DELETE FROM modify_table WHERE id IN (SELECT id FROM modify_table WHERE id = 1) RETURNING *
)
SELECT * FROM deleted_rows;
 id | val
---------------------------------------------------------------------
  1 |   2
(1 row)

WITH deleted_rows AS (
	DELETE FROM modify_table WHERE id IN (SELECT id FROM modify_table WHERE val = 4) RETURNING *
)
SELECT * FROM deleted_rows;
 id | val
---------------------------------------------------------------------
  2 |   4
(1 row)

WITH select_rows AS (
	SELECT id FROM modify_table WHERE val = 4
),
deleted_rows AS (
	DELETE FROM modify_table WHERE id IN (SELECT id FROM select_rows) RETURNING *
)
SELECT * FROM deleted_rows;
 id | val
---------------------------------------------------------------------
(0 rows)

WITH deleted_rows AS (
	DELETE FROM modify_table WHERE val IN (SELECT val FROM modify_table WHERE id = 3) RETURNING *
)
SELECT * FROM deleted_rows;
 id | val
---------------------------------------------------------------------
  3 |   6
(1 row)

WITH select_rows AS (
	SELECT val FROM modify_table WHERE id = 3
),
deleted_rows AS (
	DELETE FROM modify_table WHERE val IN (SELECT val FROM select_rows) RETURNING *
)
SELECT * FROM deleted_rows;
 id | val
---------------------------------------------------------------------
(0 rows)

WITH deleted_rows AS (
	DELETE FROM modify_table WHERE ctid IN (SELECT ctid FROM modify_table WHERE id = 1) RETURNING *
)
SELECT * FROM deleted_rows;
ERROR:  cannot perform distributed planning for the given modification
DETAIL:  Recursively planned distributed modifications with ctid on where clause are not supported.
WITH select_rows AS (
	SELECT ctid FROM modify_table WHERE id = 1
),
deleted_rows AS (
	DELETE FROM modify_table WHERE ctid IN (SELECT ctid FROM select_rows) RETURNING *
)
SELECT * FROM deleted_rows;
ERROR:  cannot perform distributed planning for the given modification
DETAIL:  Recursively planned distributed modifications with ctid on where clause are not supported.
WITH added_data AS (
	INSERT INTO modify_table VALUES (1,2), (1,6) RETURNING *
),
select_data AS (
	SELECT * FROM added_data WHERE id = 1
),
raw_data AS (
	DELETE FROM modify_table WHERE id = 1 AND ctid IN (SELECT ctid FROM select_data) RETURNING val
)
SELECT * FROM raw_data ORDER BY val;
ERROR:  cannot perform distributed planning for the given modification
DETAIL:  Recursively planned distributed modifications with ctid on where clause are not supported.
-- Needed becaues of CTE inlining triggering https://github.com/citusdata/citus/issues/3189
SET citus.enable_cte_inlining TO FALSE;
WITH added_data AS (
	INSERT INTO modify_table VALUES (1, trunc(10 * random())), (1, trunc(random())) RETURNING *
),
select_data AS (
	SELECT val, now() FROM added_data WHERE id = 1
),
raw_data AS (
	DELETE FROM modify_table WHERE id = 1 AND val IN (SELECT val FROM select_data) RETURNING *
)
SELECT COUNT(*) FROM raw_data;
 count
---------------------------------------------------------------------
     2
(1 row)

SET citus.enable_cte_inlining TO TRUE;
WITH added_data AS (
	INSERT INTO modify_table VALUES (1, trunc(10 * random())), (1, trunc(random())) RETURNING *
),
select_data AS (
	SELECT val, '2011-01-01' FROM added_data WHERE id = 1
),
raw_data AS (
	DELETE FROM modify_table WHERE id = 1 AND val IN (SELECT val FROM select_data) RETURNING *
)
SELECT COUNT(*) FROM raw_data;
 count
---------------------------------------------------------------------
     2
(1 row)

INSERT INTO modify_table VALUES (1,2), (1,6), (2, 3), (3, 5);
WITH select_data AS (
	SELECT * FROM modify_table
),
raw_data AS (
	DELETE FROM modify_table WHERE id IN (SELECT id FROM select_data WHERE val > 5) RETURNING id, val
)
SELECT * FROM raw_data ORDER BY val;
 id | val
---------------------------------------------------------------------
  1 |   2
  1 |   6
(2 rows)

WITH select_data AS (
	SELECT * FROM modify_table
),
raw_data AS (
	UPDATE modify_table SET val = 0 WHERE id IN (SELECT id FROM select_data WHERE val < 5) RETURNING id, val
)
SELECT * FROM raw_data ORDER BY val;
 id | val
---------------------------------------------------------------------
  2 |   0
(1 row)

SELECT * FROM modify_table ORDER BY id, val;
 id | val
---------------------------------------------------------------------
  2 |   0
  3 |   5
(2 rows)

-- Test with joins
WITH select_data AS (
	SELECT * FROM modify_table
),
raw_data AS (
	UPDATE modify_table SET val = 0 WHERE
		id IN (SELECT id FROM select_data) AND
		val IN (SELECT counter FROM summary_table)
	RETURNING id, val
)
SELECT * FROM raw_data ORDER BY val;
 id | val
---------------------------------------------------------------------
(0 rows)

-- Test with replication factor 2
SET citus.shard_replication_factor to 2;
DROP TABLE modify_table;
CREATE TABLE with_modifying.modify_table (id int, val int);
SELECT create_distributed_table('modify_table', 'id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

INSERT INTO with_modifying.modify_table SELECT user_id, value_1 FROM public.users_table;
DROP TABLE summary_table;
CREATE TABLE with_modifying.summary_table (id int, counter int);
SELECT create_distributed_table('summary_table', 'id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
   107
(1 row)

SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
(0 rows)

WITH raw_data AS (
	DELETE FROM modify_table RETURNING *
)
INSERT INTO summary_table SELECT id, COUNT(*) AS counter FROM raw_data GROUP BY id;
SELECT COUNT(*) FROM modify_table;
 count
---------------------------------------------------------------------
     0
(1 row)

SELECT * FROM summary_table ORDER BY id, counter;
 id | counter
---------------------------------------------------------------------
  1 |       8
  2 |      19
  3 |      18
  4 |      24
  5 |      27
  6 |      11
(6 rows)

-- make sure that the intermediate result uses a connection
-- that does not interfere with placement connections
BEGIN;
	INSERT INTO modify_table (id) VALUES (10000);
	WITH test_cte AS (SELECT count(*) FROM modify_table) SELECT * FROM test_cte;
 count
---------------------------------------------------------------------
     1
(1 row)

ROLLBACK;
-- similarly, make sure that the intermediate result uses a seperate connection
 WITH first_query AS (INSERT INTO modify_table (id) VALUES (10001)),
 	second_query AS (SELECT * FROM modify_table) SELECT count(*) FROM second_query;
 count
---------------------------------------------------------------------
     1
(1 row)

DROP SCHEMA with_modifying CASCADE;
NOTICE:  drop cascades to 5 other objects
DETAIL:  drop cascades to table users_table
drop cascades to table anchor_table
drop cascades to function raise_failed_execution_cte(text)
drop cascades to table modify_table
drop cascades to table summary_table
