From 7973158e0e9924dfdbdea0ea774f173e39ee6804 Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Sun, 24 Nov 2019 08:37:34 -0500 Subject: [PATCH] opt: push Limit through Left Joins If we limit the results of a left join, we can also limit the join's left input (because each left row generates at least one output row). This is useful with a common pattern of queries where we have a "core" query followed by a series of left joins that populate the rows with extra information. Release note (performance improvement): we generate better plans in many cases where the query has LEFT / RIGHT JOINs and also has LIMIT. --- pkg/sql/opt/norm/rules/limit.opt | 60 ++ pkg/sql/opt/norm/testdata/rules/decorrelate | 20 +- pkg/sql/opt/norm/testdata/rules/limit | 666 +++++++++++++++++++- 3 files changed, 737 insertions(+), 9 deletions(-) diff --git a/pkg/sql/opt/norm/rules/limit.opt b/pkg/sql/opt/norm/rules/limit.opt index 1215c7fb2ad4..630def5b5166 100644 --- a/pkg/sql/opt/norm/rules/limit.opt +++ b/pkg/sql/opt/norm/rules/limit.opt @@ -115,3 +115,63 @@ $input ) $private ) + +# PushLimitIntoLeftJoin pushes a Limit into the left input of a left join. Since +# the left join creates an output row for each left input row, we only need that +# many rows from that input. We can only do this if the limit ordering refers +# only to the left input columns. We also check that the cardinality of the left +# input is more than the limit, to prevent repeated applications of the rule. +[PushLimitIntoLeftJoin, Normalize] +(Limit + $input:(LeftJoin + $left:* + $right:* + $on:* + $private:* + ) + (Const $limit:*) & ^(LimitGeMaxRows $limit $left) + $ordering:* & (HasColsInOrdering $left $ordering) +) +=> +(Limit + (LeftJoin + (Limit + $left + (Const $limit) + (PruneOrdering $ordering (OutputCols $left)) + ) + $right + $on + $private + ) + (Const $limit) + $ordering +) + +# PushLimitIntoRightJoin is symmetric with PushLimitIntoLeftJoin. +[PushLimitIntoRightJoin, Normalize] +(Limit + $input:(RightJoin + $left:* + $right:* + $on:* + $private:* + ) + (Const $limit:*) & ^(LimitGeMaxRows $limit $right) + $ordering:* & (HasColsInOrdering $right $ordering) +) +=> +(Limit + (RightJoin + $left + (Limit + $right + (Const $limit) + (PruneOrdering $ordering (OutputCols $right)) + ) + $on + $private + ) + (Const $limit) + $ordering +) diff --git a/pkg/sql/opt/norm/testdata/rules/decorrelate b/pkg/sql/opt/norm/testdata/rules/decorrelate index 5439e1441d3c..08aeddb0e777 100644 --- a/pkg/sql/opt/norm/testdata/rules/decorrelate +++ b/pkg/sql/opt/norm/testdata/rules/decorrelate @@ -4108,16 +4108,10 @@ values ├── cardinality: [0 - 1] ├── key: () ├── fd: ()-->(2,7,9,12) - ├── left-join (hash) + ├── right-join (hash) │ ├── columns: i:2(int) y:7(int) true:9(bool) rownum:12(int!null) - │ ├── fd: (12)-->(2), ()~~>(9) + │ ├── fd: ()-->(2,12), ()~~>(9) │ ├── limit hint: 1.00 - │ ├── ordinality - │ │ ├── columns: i:2(int) rownum:12(int!null) - │ │ ├── key: (12) - │ │ ├── fd: (12)-->(2) - │ │ └── scan a - │ │ └── columns: i:2(int) │ ├── project │ │ ├── columns: true:9(bool!null) y:7(int) │ │ ├── fd: ()-->(9) @@ -4125,6 +4119,16 @@ values │ │ │ └── columns: y:7(int) │ │ └── projections │ │ └── true [type=bool] + │ ├── ordinality + │ │ ├── columns: i:2(int) rownum:12(int!null) + │ │ ├── cardinality: [0 - 1] + │ │ ├── key: () + │ │ ├── fd: ()-->(2,12) + │ │ └── scan a + │ │ ├── columns: i:2(int) + │ │ ├── limit: 1 + │ │ ├── key: () + │ │ └── fd: ()-->(2) │ └── filters │ └── y = i [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)] └── const: 1 [type=int] diff --git a/pkg/sql/opt/norm/testdata/rules/limit b/pkg/sql/opt/norm/testdata/rules/limit index be5082eed5cd..4e7f2b4b75b7 100644 --- a/pkg/sql/opt/norm/testdata/rules/limit +++ b/pkg/sql/opt/norm/testdata/rules/limit @@ -3,7 +3,15 @@ CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT, s STRING, j JSON) ---- exec-ddl -CREATE TABLE t.b (x INT PRIMARY KEY, y INT) +CREATE TABLE b (x INT PRIMARY KEY, y INT) +---- + +exec-ddl +CREATE TABLE ab (a INT PRIMARY KEY, b INT) +---- + +exec-ddl +CREATE TABLE uv (u INT PRIMARY KEY, v INT) ---- # -------------------------------------------------- @@ -628,3 +636,659 @@ limit │ ├── fd: (1)-->(2-5) │ └── limit hint: 10.00 └── const: 10 [type=int] + +# --------------------- +# PushLimitIntoLeftJoin +# --------------------- + +norm expect=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── limit + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (1) + │ │ ├── fd: (1)-->(2) + │ │ ├── scan ab + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2) + │ │ │ └── limit hint: 10.00 + │ │ └── const: 10 [type=int] + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering can be pushed down. +norm expect=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY a LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +1 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +1 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +1 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── internal-ordering: +1 + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (1) + │ │ ├── fd: (1)-->(2) + │ │ ├── scan ab + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2) + │ │ │ ├── ordering: +1 + │ │ │ └── limit hint: 10.00 + │ │ └── const: 10 [type=int] + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY b LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +2 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +2 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +2 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── internal-ordering: +2 + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (1) + │ │ ├── fd: (1)-->(2) + │ │ ├── sort + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2) + │ │ │ ├── ordering: +2 + │ │ │ ├── limit hint: 10.00 + │ │ │ └── scan ab + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── key: (1) + │ │ │ └── fd: (1)-->(2) + │ │ └── const: 10 [type=int] + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering on u is not equivalent to ordering on a because of NULLs; it cannot +# be pushed down. +norm expect-not=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY u LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +3 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +3 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +3 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering cannot be pushed down. +norm expect-not=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY v LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +4 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +4 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +4 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect-not=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY a, v LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +1,+4 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +1,+4 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +1,+4 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect-not=PushLimitIntoLeftJoin +SELECT * FROM ab LEFT JOIN uv ON a = u ORDER BY u, b LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── internal-ordering: +3,+2 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +3,+2 + ├── sort + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +3,+2 + │ ├── limit hint: 10.00 + │ └── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Rule should not fire if the input's cardinality is already less than the +# limit. +norm expect-not=PushLimitIntoLeftJoin +SELECT * FROM (SELECT * FROM ab LIMIT 5) LEFT JOIN uv ON a = u LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── limit + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── cardinality: [0 - 5] + │ │ ├── key: (1) + │ │ ├── fd: (1)-->(2) + │ │ ├── scan ab + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2) + │ │ │ └── limit hint: 5.00 + │ │ └── const: 5 [type=int] + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Push the limit even if the input is already limited (but with a higher limit). +norm expect=PushLimitIntoLeftJoin +SELECT * FROM (SELECT * FROM ab LIMIT 20) LEFT JOIN uv ON a = u LIMIT 10 +---- +limit + ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── left-join (hash) + │ ├── columns: a:1(int!null) b:2(int) u:3(int) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── limit + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (1) + │ │ ├── fd: (1)-->(2) + │ │ ├── limit + │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ ├── cardinality: [0 - 20] + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2) + │ │ │ ├── limit hint: 10.00 + │ │ │ ├── scan ab + │ │ │ │ ├── columns: a:1(int!null) b:2(int) + │ │ │ │ ├── key: (1) + │ │ │ │ ├── fd: (1)-->(2) + │ │ │ │ └── limit hint: 20.00 + │ │ │ └── const: 20 [type=int] + │ │ └── const: 10 [type=int] + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# ---------------------- +# PushLimitIntoRightJoin +# ---------------------- + +norm expect=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── limit + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (3) + │ │ ├── fd: (3)-->(4) + │ │ ├── scan uv + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── key: (3) + │ │ │ ├── fd: (3)-->(4) + │ │ │ └── limit hint: 10.00 + │ │ └── const: 10 [type=int] + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering can be pushed down. +norm expect=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY u LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +3 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +3 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +3 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── limit + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── internal-ordering: +3 + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (3) + │ │ ├── fd: (3)-->(4) + │ │ ├── scan uv + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── key: (3) + │ │ │ ├── fd: (3)-->(4) + │ │ │ ├── ordering: +3 + │ │ │ └── limit hint: 10.00 + │ │ └── const: 10 [type=int] + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY v LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +4 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +4 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +4 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── limit + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── internal-ordering: +4 + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (3) + │ │ ├── fd: (3)-->(4) + │ │ ├── sort + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── key: (3) + │ │ │ ├── fd: (3)-->(4) + │ │ │ ├── ordering: +4 + │ │ │ ├── limit hint: 10.00 + │ │ │ └── scan uv + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── key: (3) + │ │ │ └── fd: (3)-->(4) + │ │ └── const: 10 [type=int] + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering on a is not equivalent to ordering on u because of NULLs; it cannot +# be pushed down. +norm expect-not=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY a LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +1 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +1 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +1 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Ordering cannot be pushed down. +norm expect-not=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY b LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +2 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +2 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +2 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect-not=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY u, b LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +3,+2 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +3,+2 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +3,+2 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +norm expect-not=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN uv ON a = u ORDER BY a, v LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── internal-ordering: +1,+4 + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── ordering: +1,+4 + ├── sort + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── ordering: +1,+4 + │ ├── limit hint: 10.00 + │ └── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── scan uv + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── key: (3) + │ │ └── fd: (3)-->(4) + │ └── filters + │ └── a = u [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)] + └── const: 10 [type=int] + +# Rule should not fire if the input's cardinality is already less than the +# limit. +norm expect-not=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN (SELECT * FROM uv LIMIT 5) ON b = v LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── limit + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── cardinality: [0 - 5] + │ │ ├── key: (3) + │ │ ├── fd: (3)-->(4) + │ │ ├── scan uv + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── key: (3) + │ │ │ ├── fd: (3)-->(4) + │ │ │ └── limit hint: 5.00 + │ │ └── const: 5 [type=int] + │ └── filters + │ └── b = v [type=bool, outer=(2,4), constraints=(/2: (/NULL - ]; /4: (/NULL - ]), fd=(2)==(4), (4)==(2)] + └── const: 10 [type=int] + +# Push the limit even if the input is already limited (but with a higher limit). +norm expect=PushLimitIntoRightJoin +SELECT * FROM ab RIGHT JOIN (SELECT * FROM uv LIMIT 20) ON b = v LIMIT 10 +---- +limit + ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + ├── cardinality: [0 - 10] + ├── key: (1,3) + ├── fd: (1)-->(2), (3)-->(4) + ├── right-join (hash) + │ ├── columns: a:1(int) b:2(int) u:3(int!null) v:4(int) + │ ├── key: (1,3) + │ ├── fd: (1)-->(2), (3)-->(4) + │ ├── limit hint: 10.00 + │ ├── scan ab + │ │ ├── columns: a:1(int!null) b:2(int) + │ │ ├── key: (1) + │ │ └── fd: (1)-->(2) + │ ├── limit + │ │ ├── columns: u:3(int!null) v:4(int) + │ │ ├── cardinality: [0 - 10] + │ │ ├── key: (3) + │ │ ├── fd: (3)-->(4) + │ │ ├── limit + │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ ├── cardinality: [0 - 20] + │ │ │ ├── key: (3) + │ │ │ ├── fd: (3)-->(4) + │ │ │ ├── limit hint: 10.00 + │ │ │ ├── scan uv + │ │ │ │ ├── columns: u:3(int!null) v:4(int) + │ │ │ │ ├── key: (3) + │ │ │ │ ├── fd: (3)-->(4) + │ │ │ │ └── limit hint: 20.00 + │ │ │ └── const: 20 [type=int] + │ │ └── const: 10 [type=int] + │ └── filters + │ └── b = v [type=bool, outer=(2,4), constraints=(/2: (/NULL - ]; /4: (/NULL - ]), fd=(2)==(4), (4)==(2)] + └── const: 10 [type=int]