Skip to content

Commit cf4f56a

Browse files
hanweimy-ship-it
authored andcommitted
Fix readable CTE with SELECT INTO clause.
In #1215 bugfix, it add a check for writable CTE with SELECT INTO clause. It will invoke `transformWithClause` twice, even it's readable CTE. But Cloudberry currently only support one WITH clause per query level, so it will lead to crash. So we can give up invoking `transformWithClause` function when check and iterate through each CTE to verify if it is writable. At the same time, we add comprehensive test cases to validate.
1 parent cf9a6ea commit cf4f56a

4 files changed

Lines changed: 218 additions & 15 deletions

File tree

src/backend/parser/analyze.c

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -343,21 +343,29 @@ transformOptionalSelectInto(ParseState *pstate, Node *parseTree)
343343

344344
if (stmt->withClause)
345345
{
346-
/*
347-
* Just transform to check p_hasModifyingCTE, cte list will be transformed inside SELECT stmt.
348-
*/
349-
transformWithClause(pstate, stmt->withClause);
350-
/*
351-
* Since Cloudberry currently only support a single writer gang, only one
352-
* writable clause is permitted per CTE. Once we get flexible gangs
353-
* with more than one writer gang we can lift this restriction.
354-
*/
355-
if (pstate->p_hasModifyingCTE)
356-
ereport(ERROR,
357-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
358-
errmsg("writable CTE queries cannot be used with writable queries"),
359-
errdetail("Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context."),
360-
errhint("Rewrite the query to only include one writable clause.")));
346+
ListCell *lc;
347+
foreach(lc, stmt->withClause->ctes)
348+
{
349+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
350+
if (!IsA(cte->ctequery, SelectStmt))
351+
{
352+
/* must be a data-modifying statement */
353+
Assert(IsA(cte->ctequery, InsertStmt) ||
354+
IsA(cte->ctequery, UpdateStmt) ||
355+
IsA(cte->ctequery, DeleteStmt));
356+
357+
/*
358+
* Since Cloudberry currently only support a single writer gang, only one
359+
* writable clause is permitted per CTE. Once we get flexible gangs
360+
* with more than one writer gang we can lift this restriction.
361+
*/
362+
ereport(ERROR,
363+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
364+
errmsg("writable CTE queries cannot be used with writable queries"),
365+
errdetail("Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context."),
366+
errhint("Rewrite the query to only include one writable clause.")));
367+
}
368+
}
361369
}
362370
}
363371
}

src/test/regress/expected/with.out

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3264,3 +3264,76 @@ ERROR: writable CTE queries cannot be used with writable queries
32643264
DETAIL: Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context.
32653265
HINT: Rewrite the query to only include one writable clause.
32663266
DROP TABLE t_w_cte_relp;
3267+
--
3268+
-- readable CTE with SELECT INTO clause.
3269+
--
3270+
WITH sel AS (
3271+
SELECT 1 as a
3272+
)
3273+
SELECT a INTO t_r_cte FROM sel;
3274+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3275+
SELECT * FROM t_r_cte;
3276+
a
3277+
---
3278+
1
3279+
(1 row)
3280+
3281+
DROP TABLE t_r_cte;
3282+
--
3283+
-- Multiple readable CTEs with SELECT INTO
3284+
--
3285+
WITH cte1 AS (SELECT 1 as a),
3286+
cte2 AS (SELECT 2 as b)
3287+
SELECT a, b INTO t_multi_r_cte FROM cte1, cte2;
3288+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3289+
SELECT * FROM t_multi_r_cte;
3290+
a | b
3291+
---+---
3292+
1 | 2
3293+
(1 row)
3294+
3295+
DROP TABLE t_multi_r_cte;
3296+
--
3297+
-- Nested SELECT in CTE with SELECT INTO
3298+
--
3299+
WITH nested_cte AS (
3300+
SELECT * FROM (SELECT 100 as val, 'test'::text as name) subq
3301+
)
3302+
SELECT val, name INTO t_nested_r_cte FROM nested_cte;
3303+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3304+
SELECT * FROM t_nested_r_cte;
3305+
val | name
3306+
-----+------
3307+
100 | test
3308+
(1 row)
3309+
3310+
DROP TABLE t_nested_r_cte;
3311+
--
3312+
-- CTE with JOIN and SELECT INTO
3313+
--
3314+
WITH cte1 AS (SELECT 1 as id, 'foo' as val),
3315+
cte2 AS (SELECT 1 as id, 'bar' as val)
3316+
SELECT cte1.id, cte1.val as val1, cte2.val as val2
3317+
INTO t_join_r_cte
3318+
FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
3319+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3320+
SELECT * FROM t_join_r_cte;
3321+
id | val1 | val2
3322+
----+------+------
3323+
1 | foo | bar
3324+
(1 row)
3325+
3326+
DROP TABLE t_join_r_cte;
3327+
--
3328+
-- Verify mixed readable and writable CTEs blocked (negative test)
3329+
--
3330+
CREATE TABLE t_mixed_test (a int);
3331+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
3332+
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
3333+
WITH read_cte AS (SELECT 1 as x),
3334+
write_cte AS (INSERT INTO t_mixed_test VALUES (1) RETURNING *)
3335+
SELECT x INTO t_should_fail2 FROM read_cte; -- should fail
3336+
ERROR: writable CTE queries cannot be used with writable queries
3337+
DETAIL: Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context.
3338+
HINT: Rewrite the query to only include one writable clause.
3339+
DROP TABLE t_mixed_test;

src/test/regress/expected/with_optimizer.out

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3284,3 +3284,76 @@ ERROR: writable CTE queries cannot be used with writable queries
32843284
DETAIL: Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context.
32853285
HINT: Rewrite the query to only include one writable clause.
32863286
DROP TABLE t_w_cte_relp;
3287+
--
3288+
-- readable CTE with SELECT INTO clause.
3289+
--
3290+
WITH sel AS (
3291+
SELECT 1 as a
3292+
)
3293+
SELECT a INTO t_r_cte FROM sel;
3294+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3295+
SELECT * FROM t_r_cte;
3296+
a
3297+
---
3298+
1
3299+
(1 row)
3300+
3301+
DROP TABLE t_r_cte;
3302+
--
3303+
-- Multiple readable CTEs with SELECT INTO
3304+
--
3305+
WITH cte1 AS (SELECT 1 as a),
3306+
cte2 AS (SELECT 2 as b)
3307+
SELECT a, b INTO t_multi_r_cte FROM cte1, cte2;
3308+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3309+
SELECT * FROM t_multi_r_cte;
3310+
a | b
3311+
---+---
3312+
1 | 2
3313+
(1 row)
3314+
3315+
DROP TABLE t_multi_r_cte;
3316+
--
3317+
-- Nested SELECT in CTE with SELECT INTO
3318+
--
3319+
WITH nested_cte AS (
3320+
SELECT * FROM (SELECT 100 as val, 'test'::text as name) subq
3321+
)
3322+
SELECT val, name INTO t_nested_r_cte FROM nested_cte;
3323+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3324+
SELECT * FROM t_nested_r_cte;
3325+
val | name
3326+
-----+------
3327+
100 | test
3328+
(1 row)
3329+
3330+
DROP TABLE t_nested_r_cte;
3331+
--
3332+
-- CTE with JOIN and SELECT INTO
3333+
--
3334+
WITH cte1 AS (SELECT 1 as id, 'foo' as val),
3335+
cte2 AS (SELECT 1 as id, 'bar' as val)
3336+
SELECT cte1.id, cte1.val as val1, cte2.val as val2
3337+
INTO t_join_r_cte
3338+
FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
3339+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry.
3340+
SELECT * FROM t_join_r_cte;
3341+
id | val1 | val2
3342+
----+------+------
3343+
1 | foo | bar
3344+
(1 row)
3345+
3346+
DROP TABLE t_join_r_cte;
3347+
--
3348+
-- Verify mixed readable and writable CTEs blocked (negative test)
3349+
--
3350+
CREATE TABLE t_mixed_test (a int);
3351+
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
3352+
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
3353+
WITH read_cte AS (SELECT 1 as x),
3354+
write_cte AS (INSERT INTO t_mixed_test VALUES (1) RETURNING *)
3355+
SELECT x INTO t_should_fail2 FROM read_cte; -- should fail
3356+
ERROR: writable CTE queries cannot be used with writable queries
3357+
DETAIL: Apache Cloudberry currently only support CTEs with one writable clause, called in a non-writable context.
3358+
HINT: Rewrite the query to only include one writable clause.
3359+
DROP TABLE t_mixed_test;

src/test/regress/sql/with.sql

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,3 +1594,52 @@ EXPLAIN(COSTS OFF, VERBOSE) WITH ins AS (
15941594
)
15951595
SELECT sum(a) INTO t_w_cte_relp_1 FROM ins;
15961596
DROP TABLE t_w_cte_relp;
1597+
1598+
--
1599+
-- readable CTE with SELECT INTO clause.
1600+
--
1601+
WITH sel AS (
1602+
SELECT 1 as a
1603+
)
1604+
SELECT a INTO t_r_cte FROM sel;
1605+
SELECT * FROM t_r_cte;
1606+
DROP TABLE t_r_cte;
1607+
1608+
--
1609+
-- Multiple readable CTEs with SELECT INTO
1610+
--
1611+
WITH cte1 AS (SELECT 1 as a),
1612+
cte2 AS (SELECT 2 as b)
1613+
SELECT a, b INTO t_multi_r_cte FROM cte1, cte2;
1614+
SELECT * FROM t_multi_r_cte;
1615+
DROP TABLE t_multi_r_cte;
1616+
1617+
--
1618+
-- Nested SELECT in CTE with SELECT INTO
1619+
--
1620+
WITH nested_cte AS (
1621+
SELECT * FROM (SELECT 100 as val, 'test'::text as name) subq
1622+
)
1623+
SELECT val, name INTO t_nested_r_cte FROM nested_cte;
1624+
SELECT * FROM t_nested_r_cte;
1625+
DROP TABLE t_nested_r_cte;
1626+
1627+
--
1628+
-- CTE with JOIN and SELECT INTO
1629+
--
1630+
WITH cte1 AS (SELECT 1 as id, 'foo' as val),
1631+
cte2 AS (SELECT 1 as id, 'bar' as val)
1632+
SELECT cte1.id, cte1.val as val1, cte2.val as val2
1633+
INTO t_join_r_cte
1634+
FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
1635+
SELECT * FROM t_join_r_cte;
1636+
DROP TABLE t_join_r_cte;
1637+
1638+
--
1639+
-- Verify mixed readable and writable CTEs blocked (negative test)
1640+
--
1641+
CREATE TABLE t_mixed_test (a int);
1642+
WITH read_cte AS (SELECT 1 as x),
1643+
write_cte AS (INSERT INTO t_mixed_test VALUES (1) RETURNING *)
1644+
SELECT x INTO t_should_fail2 FROM read_cte; -- should fail
1645+
DROP TABLE t_mixed_test;

0 commit comments

Comments
 (0)