diff --git a/src/backend/columnar/columnar_customscan.c b/src/backend/columnar/columnar_customscan.c index 4a5ce008a..3efcf4588 100644 --- a/src/backend/columnar/columnar_customscan.c +++ b/src/backend/columnar/columnar_customscan.c @@ -104,6 +104,9 @@ static Plan * ColumnarScanPath_PlanCustomPath(PlannerInfo *root, List *tlist, List *clauses, List *custom_plans); +static List * ColumnarScanPath_ReparameterizeCustomPathByChild(PlannerInfo *root, + List *custom_private, + RelOptInfo *child_rel); static Node * ColumnarScan_CreateCustomScanState(CustomScan *cscan); static void ColumnarScan_BeginCustomScan(CustomScanState *node, EState *estate, @@ -141,6 +144,7 @@ static int ColumnarPlannerDebugLevel = DEBUG3; const struct CustomPathMethods ColumnarScanPathMethods = { .CustomName = "ColumnarScan", .PlanCustomPath = ColumnarScanPath_PlanCustomPath, + .ReparameterizeCustomPathByChild = ColumnarScanPath_ReparameterizeCustomPathByChild, }; const struct CustomScanMethods ColumnarScanScanMethods = { @@ -1316,6 +1320,66 @@ ColumnarScanPath_PlanCustomPath(PlannerInfo *root, } +/* + * ReparameterizeMutator changes all varnos referencing the topmost parent of + * child_rel to instead reference child_rel directly. + */ +static Node * +ReparameterizeMutator(Node *node, RelOptInfo *child_rel) +{ + if (node == NULL) + { + return NULL; + } + if (IsA(node, Var)) + { + Var *var = castNode(Var, node); + if (bms_is_member(var->varno, child_rel->top_parent_relids)) + { + var = copyObject(var); + var->varno = child_rel->relid; + } + return (Node *) var; + } + + if (IsA(node, RestrictInfo)) + { + RestrictInfo *rinfo = castNode(RestrictInfo, node); + rinfo = copyObject(rinfo); + rinfo->clause = (Expr *) expression_tree_mutator( + (Node *) rinfo->clause, ReparameterizeMutator, (void *) child_rel); + return (Node *) rinfo; + } + return expression_tree_mutator(node, ReparameterizeMutator, + (void *) child_rel); +} + + +/* + * ColumnarScanPath_ReparameterizeCustomPathByChild is a method called when a + * path is reparameterized directly to a child relation, rather than the + * top-level parent. + * + * For instance, let there be a join of two partitioned columnar relations PX + * and PY. A path for a ColumnarScan of PY3 might be parameterized by PX so + * that the join qual "PY3.a = PX.a" (referencing the parent PX) can be pushed + * down. But if the planner decides on a partition-wise join, then the path + * will be reparameterized on the child table PX3 directly. + * + * When that happens, we need to update all Vars in the pushed-down quals to + * reference PX3, not PX, to match the new parameterization. This method + * notifies us that it needs to be done, and allows us to update the + * information in custom_private. + */ +static List * +ColumnarScanPath_ReparameterizeCustomPathByChild(PlannerInfo *root, + List *custom_private, + RelOptInfo *child_rel) +{ + return (List *) ReparameterizeMutator((Node *) custom_private, child_rel); +} + + static Node * ColumnarScan_CreateCustomScanState(CustomScan *cscan) { diff --git a/src/test/regress/expected/columnar_partitioning.out b/src/test/regress/expected/columnar_partitioning.out index cd91f665f..e84953a0b 100644 --- a/src/test/regress/expected/columnar_partitioning.out +++ b/src/test/regress/expected/columnar_partitioning.out @@ -414,3 +414,104 @@ DROP TABLE i_col CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table ij_col_row drop cascades to table ij_col_col +-- +-- https://github.com/citusdata/citus/issues/5257 +-- +set default_table_access_method to columnar; +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +SET enable_partitionwise_join to true; +EXPLAIN (costs off, timing off, summary off) +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + QUERY PLAN +--------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p1 t1_1 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2_1.a = t3_1.b) + -> Custom Scan (ColumnarScan) on prt1_p1 t2_1 + Filter: (t1_1.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1_1.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p1 t3_1 + Columnar Projected Columns: a, b + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p2 t1_2 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2_2.a = t3_2.b) + -> Custom Scan (ColumnarScan) on prt1_p2 t2_2 + Filter: (t1_2.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1_2.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p2 t3_2 + Columnar Projected Columns: a, b + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p3 t1_3 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2_3.a = t3_3.b) + -> Custom Scan (ColumnarScan) on prt1_p3 t2_3 + Filter: (t1_3.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1_3.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p3 t3_3 + Columnar Projected Columns: a, b +(45 rows) + +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + a | b | c | t2a | t3a | least +--------------------------------------------------------------------- + 0 | 0 | 0000 | 0 | 0 | 0 + 50 | 0 | 0050 | | | + 100 | 0 | 0100 | | | + 150 | 0 | 0150 | 150 | 0 | 150 + 200 | 0 | 0200 | | | + 250 | 0 | 0250 | | | + 300 | 0 | 0300 | 300 | 0 | 300 + 350 | 0 | 0350 | | | + 400 | 0 | 0400 | | | + 450 | 0 | 0450 | 450 | 0 | 450 + 500 | 0 | 0500 | | | + 550 | 0 | 0550 | | | +(12 rows) + +set default_table_access_method to default; +SET enable_partitionwise_join to default; +DROP TABLE prt1; +DROP TABLE prt2; diff --git a/src/test/regress/expected/columnar_partitioning_1.out b/src/test/regress/expected/columnar_partitioning_1.out index a3da26c42..4a9776db7 100644 --- a/src/test/regress/expected/columnar_partitioning_1.out +++ b/src/test/regress/expected/columnar_partitioning_1.out @@ -414,3 +414,104 @@ DROP TABLE i_col CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table ij_col_row drop cascades to table ij_col_col +-- +-- https://github.com/citusdata/citus/issues/5257 +-- +set default_table_access_method to columnar; +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +SET enable_partitionwise_join to true; +EXPLAIN (costs off, timing off, summary off) +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + QUERY PLAN +--------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p1 t1 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2.a = t3.b) + -> Custom Scan (ColumnarScan) on prt1_p1 t2 + Filter: (t1.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p1 t3 + Columnar Projected Columns: a, b + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p2 t1_1 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2_1.a = t3_1.b) + -> Custom Scan (ColumnarScan) on prt1_p2 t2_1 + Filter: (t1_1.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1_1.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p2 t3_1 + Columnar Projected Columns: a, b + -> Nested Loop Left Join + -> Custom Scan (ColumnarScan) on prt1_p3 t1_2 + Filter: (b = 0) + Columnar Projected Columns: a, b, c + Columnar Chunk Group Filters: (b = 0) + -> Hash Join + Hash Cond: (t2_2.a = t3_2.b) + -> Custom Scan (ColumnarScan) on prt1_p3 t2_2 + Filter: (t1_2.a = a) + Columnar Projected Columns: a + Columnar Chunk Group Filters: (t1_2.a = a) + -> Hash + -> Custom Scan (ColumnarScan) on prt2_p3 t3_2 + Columnar Projected Columns: a, b +(45 rows) + +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + a | b | c | t2a | t3a | least +--------------------------------------------------------------------- + 0 | 0 | 0000 | 0 | 0 | 0 + 50 | 0 | 0050 | | | + 100 | 0 | 0100 | | | + 150 | 0 | 0150 | 150 | 0 | 150 + 200 | 0 | 0200 | | | + 250 | 0 | 0250 | | | + 300 | 0 | 0300 | 300 | 0 | 300 + 350 | 0 | 0350 | | | + 400 | 0 | 0400 | | | + 450 | 0 | 0450 | 450 | 0 | 450 + 500 | 0 | 0500 | | | + 550 | 0 | 0550 | | | +(12 rows) + +set default_table_access_method to default; +SET enable_partitionwise_join to default; +DROP TABLE prt1; +DROP TABLE prt2; diff --git a/src/test/regress/sql/columnar_partitioning.sql b/src/test/regress/sql/columnar_partitioning.sql index e76589857..8ae26ee3e 100644 --- a/src/test/regress/sql/columnar_partitioning.sql +++ b/src/test/regress/sql/columnar_partitioning.sql @@ -146,3 +146,46 @@ DROP TABLE ij_row_row; DROP TABLE i_row CASCADE; DROP TABLE i_col CASCADE; + +-- +-- https://github.com/citusdata/citus/issues/5257 +-- + +set default_table_access_method to columnar; +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; + +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; + +SET enable_partitionwise_join to true; + +EXPLAIN (costs off, timing off, summary off) +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + +SELECT * FROM + prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) + FROM prt1 t2 + JOIN prt2 t3 ON (t2.a = t3.b) + ) ss + ON t1.a = ss.t2a WHERE t1.b = 0 + ORDER BY t1.a; + +set default_table_access_method to default; +SET enable_partitionwise_join to default; +DROP TABLE prt1; +DROP TABLE prt2;