Columnar: add method ReparameterizeCustomPathByChild. (#5275)

When performing a partition-wise join, the planner will adjust paths
parameterized by the parent rel to instead parameterize by the child
rel directly. When this reparameterization happens, we also need to
adjust the join quals to reference the child rather than the parent.

Fixes #5257.
pull/5261/head
jeff-davis 2021-09-13 10:33:48 -07:00 committed by GitHub
parent ea61efb63a
commit d48ceee238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 309 additions and 0 deletions

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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;