diff --git a/pkg/sql/logictest/testdata/logic_test/inverted_join_multi_column b/pkg/sql/logictest/testdata/logic_test/inverted_join_multi_column index 5068deac13e4..15a2128bbae4 100644 --- a/pkg/sql/logictest/testdata/logic_test/inverted_join_multi_column +++ b/pkg/sql/logictest/testdata/logic_test/inverted_join_multi_column @@ -494,8 +494,8 @@ ORDER BY (lk, rk) 5 12 5 16 -# Regression test for #59615. Ensure that invalid inverted joins are not created -# for left and anti joins. +# Regression test for #59615 and #78681. Ensure that invalid inverted joins are +# not created for left, semi, and anti joins. statement ok CREATE TABLE t59615_inv ( x INT NOT NULL CHECK (x in (1, 3)), @@ -517,3 +517,13 @@ SELECT * FROM (VALUES ('"a"'::jsonb), ('"b"'::jsonb)) AS u(y) WHERE NOT EXISTS ( ---- "a" "b" + +statement ok +INSERT INTO t59615_inv VALUES (1, '"a"'::JSONB), (3, '"a"'::JSONB) + +query T rowsort +SELECT * FROM (VALUES ('"a"'::jsonb), ('"b"'::jsonb)) AS u(y) WHERE EXISTS ( + SELECT * FROM t59615_inv t WHERE t.y @> u.y +) +---- +"a" diff --git a/pkg/sql/logictest/testdata/logic_test/lookup_join b/pkg/sql/logictest/testdata/logic_test/lookup_join index b2c683490e31..89b634101e3f 100644 --- a/pkg/sql/logictest/testdata/logic_test/lookup_join +++ b/pkg/sql/logictest/testdata/logic_test/lookup_join @@ -665,3 +665,31 @@ SELECT * FROM (VALUES (1, 10), (2, 20), (3, NULL)) AS u(w, x) WHERE NOT EXISTS ( ) ---- 3 NULL + +# Regression test for #79384. Do not generate unnecessary cross-joins on a +# lookup join's input when the lookup join uses a lookup expression. +statement ok +CREATE TABLE t79384a ( + k INT NOT NULL +) + +statement ok +CREATE TABLE t79384b ( + a INT, + b INT, + c INT, + INDEX (a, b, c) +) + +statement ok +INSERT INTO t79384a VALUES (1) + +statement ok +INSERT INTO t79384b VALUES (1, 1, 1) + +# The joined tables have a single row each, so this query should never return +# more than one row. +query I +SELECT k FROM t79384a INNER LOOKUP JOIN t79384b ON k = a AND b IN (1, 2, 3) AND c > 0 +---- +1 diff --git a/pkg/sql/opt/exec/execbuilder/testdata/inner-join b/pkg/sql/opt/exec/execbuilder/testdata/inner-join index 118877f28319..64b74dcc85a8 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/inner-join +++ b/pkg/sql/opt/exec/execbuilder/testdata/inner-join @@ -248,4 +248,3 @@ vectorized: true missing stats table: def@def_pkey spans: FULL SCAN - diff --git a/pkg/sql/opt/exec/execbuilder/testdata/lookup_join_spans b/pkg/sql/opt/exec/execbuilder/testdata/lookup_join_spans index c8794c3c77c8..a2dd57f22e9e 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/lookup_join_spans +++ b/pkg/sql/opt/exec/execbuilder/testdata/lookup_join_spans @@ -837,16 +837,14 @@ vectorized: true │ equality cols are key │ └── • lookup join + │ estimated row count: 0 │ table: metric_values@secondary │ lookup condition: ((metric_id = id) AND (nullable = 1)) AND ("time" < '2020-01-01 00:00:10+00:00') │ - └── • render - │ estimated row count: 1 - │ - └── • scan - estimated row count: 1 (10% of the table; stats collected ago) - table: metrics@name_index - spans: [/'cpu' - /'cpu'] + └── • scan + estimated row count: 1 (10% of the table; stats collected ago) + table: metrics@name_index + spans: [/'cpu' - /'cpu'] # Regression test for issue #68200. This ensures that we properly construct the diff --git a/pkg/sql/opt/xform/join_funcs.go b/pkg/sql/opt/xform/join_funcs.go index 6d651d51fc86..af4155645a83 100644 --- a/pkg/sql/opt/xform/join_funcs.go +++ b/pkg/sql/opt/xform/join_funcs.go @@ -462,7 +462,7 @@ func (c *CustomFuncs) generateLookupJoinsImpl( // because constructing a cross join with foundVals will // increase the size of the input. As a result, non-matching // input rows will show up more than once in the output, - // which is incorrect (see #59615 and #78685). + // which is incorrect (see #59615 and #78681). shouldBuildMultiSpanLookupJoin = true break } @@ -534,6 +534,9 @@ func (c *CustomFuncs) generateLookupJoinsImpl( // Reset KeyCols since we're not using it anymore. lookupJoin.KeyCols = opt.ColList{} + // Reset input since we don't need any constant values that may have + // been joined on the input above. + lookupJoin.Input = input } if len(lookupJoin.KeyCols) == 0 && len(lookupJoin.LookupExpr) == 0 { @@ -1029,11 +1032,15 @@ func (c *CustomFuncs) GenerateInvertedJoins( return } - if len(foundVals) > 1 && (joinType == opt.LeftJoinOp || joinType == opt.AntiJoinOp) { - // We cannot create an inverted join in this case, because constructing - // a cross join with foundVals will increase the size of the input. As a - // result, non-matching input rows will show up more than once in the - // output, which is incorrect (see #59615). + if len(foundVals) > 1 && + (joinType == opt.LeftJoinOp || joinType == opt.SemiJoinOp || joinType == opt.AntiJoinOp) { + // We cannot create an inverted join in this case, because + // constructing a cross join with foundVals will increase the + // size of the input. As a result, matching input rows will show + // up more than once in the output of a semi-join, and + // non-matching input rows will show up more than once in the + // output of a left or anti join, which is incorrect (see #59615 + // and #78681). // TODO(rytaft,mgartner): find a way to create an inverted join for this // case. return diff --git a/pkg/sql/opt/xform/testdata/external/tpce b/pkg/sql/opt/xform/testdata/external/tpce index fdede1bb7562..7f6622f83044 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce +++ b/pkg/sql/opt/xform/testdata/external/tpce @@ -4029,34 +4029,31 @@ project │ │ ├── fd: ()-->(1,8,27,30,31,45-47), (48)-->(49,50), (31)==(47), (47)==(31), (8)==(45), (45)==(8) │ │ ├── limit hint: 1.00 │ │ ├── inner-join (lookup commission_rate) - │ │ │ ├── columns: c_id:1!null c_tier:8!null cr_c_tier:45!null cr_tt_id:46!null cr_ex_id:47!null cr_from_qty:48!null cr_to_qty:49!null commission_rate.cr_rate:50!null - │ │ │ ├── key columns: [8 57] = [45 46] - │ │ │ ├── key: (47,48) - │ │ │ ├── fd: ()-->(1,8,45,46), (47,48)-->(49,50), (8)==(45), (45)==(8) - │ │ │ ├── project - │ │ │ │ ├── columns: "lookup_join_const_col_@46":57!null c_id:1!null c_tier:8!null + │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null cr_c_tier:45!null cr_tt_id:46!null cr_ex_id:47!null cr_from_qty:48!null cr_to_qty:49!null commission_rate.cr_rate:50!null + │ │ │ ├── lookup expression + │ │ │ │ └── filters + │ │ │ │ ├── cr_ex_id:47 = s_ex_id:31 [outer=(31,47), constraints=(/31: (/NULL - ]; /47: (/NULL - ]), fd=(31)==(47), (47)==(31)] + │ │ │ │ ├── cr_c_tier:45 IN (1, 2, 3) [outer=(45), constraints=(/45: [/1 - /1] [/2 - /2] [/3 - /3]; tight)] + │ │ │ │ ├── cr_tt_id:46 = 'TLS' [outer=(46), constraints=(/46: [/'TLS' - /'TLS']; tight), fd=()-->(46)] + │ │ │ │ └── cr_from_qty:48 <= 100 [outer=(48), constraints=(/48: (/NULL - /100]; tight)] + │ │ │ ├── key: (45,48) + │ │ │ ├── fd: ()-->(27,30,31,46,47), (45,48)-->(49,50), (31)==(47), (47)==(31) + │ │ │ ├── scan security + │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] │ │ │ │ ├── cardinality: [0 - 1] │ │ │ │ ├── key: () - │ │ │ │ ├── fd: ()-->(1,8,57) - │ │ │ │ ├── scan customer - │ │ │ │ │ ├── columns: c_id:1!null c_tier:8!null - │ │ │ │ │ ├── constraint: /1: [/0 - /0] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(1,8) - │ │ │ │ └── projections - │ │ │ │ └── 'TLS' [as="lookup_join_const_col_@46":57] + │ │ │ │ └── fd: ()-->(27,30,31) │ │ │ └── filters - │ │ │ ├── cr_from_qty:48 <= 100 [outer=(48), constraints=(/48: (/NULL - /100]; tight)] │ │ │ └── cr_to_qty:49 >= 200 [outer=(49), constraints=(/49: [/200 - ]; tight)] - │ │ ├── scan security - │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null - │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] + │ │ ├── scan customer + │ │ │ ├── columns: c_id:1!null c_tier:8!null + │ │ │ ├── constraint: /1: [/0 - /0] │ │ │ ├── cardinality: [0 - 1] │ │ │ ├── key: () - │ │ │ └── fd: ()-->(27,30,31) + │ │ │ └── fd: ()-->(1,8) │ │ └── filters - │ │ └── cr_ex_id:47 = s_ex_id:31 [outer=(31,47), constraints=(/31: (/NULL - ]; /47: (/NULL - ]), fd=(31)==(47), (47)==(31)] + │ │ └── cr_c_tier:45 = c_tier:8 [outer=(8,45), constraints=(/8: (/NULL - ]; /45: (/NULL - ]), fd=(8)==(45), (45)==(8)] │ └── 1 └── projections └── commission_rate.cr_rate:50::FLOAT8 [as=cr_rate:53, outer=(50), immutable] @@ -4097,34 +4094,26 @@ project │ │ ├── key: (48) │ │ ├── fd: ()-->(1,8,27,30,31,45-47), (48)-->(49,50), (8)==(45), (45)==(8), (31)==(47), (47)==(31) │ │ ├── limit hint: 1.00 - │ │ ├── project - │ │ │ ├── columns: "lookup_join_const_col_@46":54!null c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ ├── inner-join (cross) + │ │ │ ├── columns: c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null │ │ │ ├── cardinality: [0 - 1] + │ │ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) │ │ │ ├── key: () - │ │ │ ├── fd: ()-->(1,8,27,30,31,54) + │ │ │ ├── fd: ()-->(1,8,27,30,31) │ │ │ ├── limit hint: 1.00 - │ │ │ ├── inner-join (cross) - │ │ │ │ ├── columns: c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ ├── scan customer + │ │ │ │ ├── columns: c_id:1!null c_tier:8!null + │ │ │ │ ├── constraint: /1: [/0 - /0] │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) │ │ │ │ ├── key: () - │ │ │ │ ├── fd: ()-->(1,8,27,30,31) - │ │ │ │ ├── limit hint: 1.00 - │ │ │ │ ├── scan customer - │ │ │ │ │ ├── columns: c_id:1!null c_tier:8!null - │ │ │ │ │ ├── constraint: /1: [/0 - /0] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(1,8) - │ │ │ │ ├── scan security - │ │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null - │ │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(27,30,31) - │ │ │ │ └── filters (true) - │ │ │ └── projections - │ │ │ └── 'TLS' [as="lookup_join_const_col_@46":54] + │ │ │ │ └── fd: ()-->(1,8) + │ │ │ ├── scan security + │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] + │ │ │ │ ├── cardinality: [0 - 1] + │ │ │ │ ├── key: () + │ │ │ │ └── fd: ()-->(27,30,31) + │ │ │ └── filters (true) │ │ └── filters │ │ └── cr_to_qty:49 >= 200 [outer=(49), constraints=(/49: [/200 - ]; tight)] │ └── 1 diff --git a/pkg/sql/opt/xform/testdata/external/tpce-no-stats b/pkg/sql/opt/xform/testdata/external/tpce-no-stats index 1474867af9de..e25461f422b6 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce-no-stats +++ b/pkg/sql/opt/xform/testdata/external/tpce-no-stats @@ -4060,34 +4060,31 @@ project │ │ ├── fd: ()-->(1,8,27,30,31,45-47), (48)-->(49,50), (31)==(47), (47)==(31), (8)==(45), (45)==(8) │ │ ├── limit hint: 1.00 │ │ ├── inner-join (lookup commission_rate) - │ │ │ ├── columns: c_id:1!null c_tier:8!null cr_c_tier:45!null cr_tt_id:46!null cr_ex_id:47!null cr_from_qty:48!null cr_to_qty:49!null commission_rate.cr_rate:50!null - │ │ │ ├── key columns: [8 57] = [45 46] - │ │ │ ├── key: (47,48) - │ │ │ ├── fd: ()-->(1,8,45,46), (47,48)-->(49,50), (8)==(45), (45)==(8) - │ │ │ ├── project - │ │ │ │ ├── columns: "lookup_join_const_col_@46":57!null c_id:1!null c_tier:8!null + │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null cr_c_tier:45!null cr_tt_id:46!null cr_ex_id:47!null cr_from_qty:48!null cr_to_qty:49!null commission_rate.cr_rate:50!null + │ │ │ ├── lookup expression + │ │ │ │ └── filters + │ │ │ │ ├── cr_ex_id:47 = s_ex_id:31 [outer=(31,47), constraints=(/31: (/NULL - ]; /47: (/NULL - ]), fd=(31)==(47), (47)==(31)] + │ │ │ │ ├── cr_c_tier:45 IN (1, 2, 3) [outer=(45), constraints=(/45: [/1 - /1] [/2 - /2] [/3 - /3]; tight)] + │ │ │ │ ├── cr_tt_id:46 = 'TLS' [outer=(46), constraints=(/46: [/'TLS' - /'TLS']; tight), fd=()-->(46)] + │ │ │ │ └── cr_from_qty:48 <= 100 [outer=(48), constraints=(/48: (/NULL - /100]; tight)] + │ │ │ ├── key: (45,48) + │ │ │ ├── fd: ()-->(27,30,31,46,47), (45,48)-->(49,50), (31)==(47), (47)==(31) + │ │ │ ├── scan security + │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] │ │ │ │ ├── cardinality: [0 - 1] │ │ │ │ ├── key: () - │ │ │ │ ├── fd: ()-->(1,8,57) - │ │ │ │ ├── scan customer - │ │ │ │ │ ├── columns: c_id:1!null c_tier:8!null - │ │ │ │ │ ├── constraint: /1: [/0 - /0] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(1,8) - │ │ │ │ └── projections - │ │ │ │ └── 'TLS' [as="lookup_join_const_col_@46":57] + │ │ │ │ └── fd: ()-->(27,30,31) │ │ │ └── filters - │ │ │ ├── cr_from_qty:48 <= 100 [outer=(48), constraints=(/48: (/NULL - /100]; tight)] │ │ │ └── cr_to_qty:49 >= 200 [outer=(49), constraints=(/49: [/200 - ]; tight)] - │ │ ├── scan security - │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null - │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] + │ │ ├── scan customer + │ │ │ ├── columns: c_id:1!null c_tier:8!null + │ │ │ ├── constraint: /1: [/0 - /0] │ │ │ ├── cardinality: [0 - 1] │ │ │ ├── key: () - │ │ │ └── fd: ()-->(27,30,31) + │ │ │ └── fd: ()-->(1,8) │ │ └── filters - │ │ └── cr_ex_id:47 = s_ex_id:31 [outer=(31,47), constraints=(/31: (/NULL - ]; /47: (/NULL - ]), fd=(31)==(47), (47)==(31)] + │ │ └── cr_c_tier:45 = c_tier:8 [outer=(8,45), constraints=(/8: (/NULL - ]; /45: (/NULL - ]), fd=(8)==(45), (45)==(8)] │ └── 1 └── projections └── commission_rate.cr_rate:50::FLOAT8 [as=cr_rate:53, outer=(50), immutable] @@ -4128,32 +4125,25 @@ project │ │ ├── key: (48) │ │ ├── fd: ()-->(1,8,27,30,31,45-47), (48)-->(49,50), (8)==(45), (45)==(8), (31)==(47), (47)==(31) │ │ ├── limit hint: 1.00 - │ │ ├── project - │ │ │ ├── columns: "lookup_join_const_col_@46":54!null c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ ├── inner-join (cross) + │ │ │ ├── columns: c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null │ │ │ ├── cardinality: [0 - 1] + │ │ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) │ │ │ ├── key: () - │ │ │ ├── fd: ()-->(1,8,27,30,31,54) - │ │ │ ├── inner-join (cross) - │ │ │ │ ├── columns: c_id:1!null c_tier:8!null s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ ├── fd: ()-->(1,8,27,30,31) + │ │ │ ├── scan customer + │ │ │ │ ├── columns: c_id:1!null c_tier:8!null + │ │ │ │ ├── constraint: /1: [/0 - /0] │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) │ │ │ │ ├── key: () - │ │ │ │ ├── fd: ()-->(1,8,27,30,31) - │ │ │ │ ├── scan customer - │ │ │ │ │ ├── columns: c_id:1!null c_tier:8!null - │ │ │ │ │ ├── constraint: /1: [/0 - /0] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(1,8) - │ │ │ │ ├── scan security - │ │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null - │ │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] - │ │ │ │ │ ├── cardinality: [0 - 1] - │ │ │ │ │ ├── key: () - │ │ │ │ │ └── fd: ()-->(27,30,31) - │ │ │ │ └── filters (true) - │ │ │ └── projections - │ │ │ └── 'TLS' [as="lookup_join_const_col_@46":54] + │ │ │ │ └── fd: ()-->(1,8) + │ │ │ ├── scan security + │ │ │ │ ├── columns: s_symb:27!null s_name:30!null s_ex_id:31!null + │ │ │ │ ├── constraint: /27: [/'ROACH' - /'ROACH'] + │ │ │ │ ├── cardinality: [0 - 1] + │ │ │ │ ├── key: () + │ │ │ │ └── fd: ()-->(27,30,31) + │ │ │ └── filters (true) │ │ └── filters │ │ └── cr_to_qty:49 >= 200 [outer=(49), constraints=(/49: [/200 - ]; tight)] │ └── 1 diff --git a/pkg/sql/opt/xform/testdata/external/trading b/pkg/sql/opt/xform/testdata/external/trading index 1361ac983e96..594b8d32963d 100644 --- a/pkg/sql/opt/xform/testdata/external/trading +++ b/pkg/sql/opt/xform/testdata/external/trading @@ -842,31 +842,21 @@ project │ │ │ ├── key: (1,22-24) │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6), (1,22-24)-->(20,21) │ │ │ ├── ordering: +1 - │ │ │ ├── project - │ │ │ │ ├── columns: "lookup_join_const_col_@21":43!null "lookup_join_const_col_@20":42!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null + │ │ │ ├── select + │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null │ │ │ │ ├── immutable - │ │ │ │ ├── stats: [rows=19000] + │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, avgsize(1)=4, distinct(2)=13000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5601.15, null(6)=0, avgsize(6)=4] │ │ │ │ ├── key: (1) - │ │ │ │ ├── fd: ()-->(42,43), (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ ├── ordering: +1 opt(42,43) [actual: +1] - │ │ │ │ ├── select + │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) + │ │ │ │ ├── ordering: +1 + │ │ │ │ ├── scan cards │ │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null - │ │ │ │ │ ├── immutable - │ │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, avgsize(1)=4, distinct(2)=13000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5601.15, null(6)=0, avgsize(6)=4] + │ │ │ │ │ ├── stats: [rows=57000, distinct(1)=57000, null(1)=0, avgsize(1)=4, distinct(2)=39000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5700, null(6)=0, avgsize(6)=4] │ │ │ │ │ ├── key: (1) │ │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ │ ├── ordering: +1 - │ │ │ │ │ ├── scan cards - │ │ │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null - │ │ │ │ │ │ ├── stats: [rows=57000, distinct(1)=57000, null(1)=0, avgsize(1)=4, distinct(2)=39000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5700, null(6)=0, avgsize(6)=4] - │ │ │ │ │ │ ├── key: (1) - │ │ │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ │ │ └── ordering: +1 - │ │ │ │ │ └── filters - │ │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] - │ │ │ │ └── projections - │ │ │ │ ├── false [as="lookup_join_const_col_@21":43] - │ │ │ │ └── 1 [as="lookup_join_const_col_@20":42] + │ │ │ │ │ └── ordering: +1 + │ │ │ │ └── filters + │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] │ │ │ └── filters (true) │ │ ├── scan cardsinfo │ │ │ ├── columns: cardsinfo.dealerid:9!null cardsinfo.cardid:10!null cardsinfo.buyprice:11!null cardsinfo.sellprice:12!null discount:13!null desiredinventory:14!null actualinventory:15!null maxinventory:16!null cardsinfo.version:17!null diff --git a/pkg/sql/opt/xform/testdata/external/trading-mutation b/pkg/sql/opt/xform/testdata/external/trading-mutation index d88c34400d1e..269ddca5baa9 100644 --- a/pkg/sql/opt/xform/testdata/external/trading-mutation +++ b/pkg/sql/opt/xform/testdata/external/trading-mutation @@ -846,31 +846,21 @@ project │ │ │ ├── key: (1,26-28) │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6), (1,26-28)-->(24,25) │ │ │ ├── ordering: +1 - │ │ │ ├── project - │ │ │ │ ├── columns: "lookup_join_const_col_@25":49!null "lookup_join_const_col_@24":48!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null + │ │ │ ├── select + │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null │ │ │ │ ├── immutable - │ │ │ │ ├── stats: [rows=19000] + │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, avgsize(1)=4, distinct(2)=13000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5601.15, null(6)=0, avgsize(6)=4] │ │ │ │ ├── key: (1) - │ │ │ │ ├── fd: ()-->(48,49), (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ ├── ordering: +1 opt(48,49) [actual: +1] - │ │ │ │ ├── select + │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) + │ │ │ │ ├── ordering: +1 + │ │ │ │ ├── scan cards │ │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null - │ │ │ │ │ ├── immutable - │ │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, avgsize(1)=4, distinct(2)=13000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5601.15, null(6)=0, avgsize(6)=4] + │ │ │ │ │ ├── stats: [rows=57000, distinct(1)=57000, null(1)=0, avgsize(1)=4, distinct(2)=39000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5700, null(6)=0, avgsize(6)=4] │ │ │ │ │ ├── key: (1) │ │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ │ ├── ordering: +1 - │ │ │ │ │ ├── scan cards - │ │ │ │ │ │ ├── columns: id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null - │ │ │ │ │ │ ├── stats: [rows=57000, distinct(1)=57000, null(1)=0, avgsize(1)=4, distinct(2)=39000, null(2)=0, avgsize(2)=4, distinct(5)=829, null(5)=0, avgsize(5)=4, distinct(6)=5700, null(6)=0, avgsize(6)=4] - │ │ │ │ │ │ ├── key: (1) - │ │ │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6) - │ │ │ │ │ │ └── ordering: +1 - │ │ │ │ │ └── filters - │ │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] - │ │ │ │ └── projections - │ │ │ │ ├── false [as="lookup_join_const_col_@25":49] - │ │ │ │ └── 1 [as="lookup_join_const_col_@24":48] + │ │ │ │ │ └── ordering: +1 + │ │ │ │ └── filters + │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] │ │ │ └── filters (true) │ │ ├── scan cardsinfo │ │ │ ├── columns: cardsinfo.dealerid:9!null cardsinfo.cardid:10!null cardsinfo.buyprice:11!null cardsinfo.sellprice:12!null cardsinfo.discount:13!null desiredinventory:14!null actualinventory:15!null maxinventory:16!null cardsinfo.version:17!null diff --git a/pkg/sql/opt/xform/testdata/rules/join b/pkg/sql/opt/xform/testdata/rules/join index ee7d8dcb42df..d56a0cef9997 100644 --- a/pkg/sql/opt/xform/testdata/rules/join +++ b/pkg/sql/opt/xform/testdata/rules/join @@ -3955,6 +3955,34 @@ anti-join (lookup abcd) └── filters └── n:2 = c:8 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)] +# Regression test for #79384. Do not generate unnecessary cross-joins with +# constant values when generating a lookup join with a lookup expression. +exec-ddl +CREATE TABLE t79384 ( + a INT, + b INT, + c INT, + INDEX (a, b, c) +) +---- + +opt expect=GenerateLookupJoinsWithFilter +SELECT m FROM small JOIN t79384 ON b IN (1, 2, 3) AND c > 0 AND m = a +---- +project + ├── columns: m:1!null + └── inner-join (lookup t79384@t79384_a_b_c_idx) + ├── columns: m:1!null a:6!null b:7!null c:8!null + ├── lookup expression + │ └── filters + │ ├── m:1 = a:6 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + │ ├── b:7 IN (1, 2, 3) [outer=(7), constraints=(/7: [/1 - /1] [/2 - /2] [/3 - /3]; tight)] + │ └── c:8 > 0 [outer=(8), constraints=(/8: [/1 - ]; tight)] + ├── fd: (1)==(6), (6)==(1) + ├── scan small + │ └── columns: m:1 + └── filters (true) + # -------------------------------------------------- # GenerateLookupJoinsWithFilter + Partial Indexes # -------------------------------------------------- @@ -8845,38 +8873,53 @@ WHERE EXISTS ( SELECT * FROM json_arr1 AS t1 WHERE t1.j @> t2.j AND t1.i IN (3, 4) ) ---- -semi-join (lookup json_arr1 [as=t1]) +project ├── columns: k:1!null l:2 j:3 a:4 - ├── key columns: [22] = [7] - ├── lookup columns are key - ├── second join in paired joiner ├── immutable ├── key: (1) ├── fd: (1)-->(2-4) - ├── inner-join (inverted json_arr1@j_idx [as=t1]) - │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 t1.k:22!null i:23!null continuation:36 - │ ├── prefix key columns: [21] = [23] - │ ├── first join in paired joiner; continuation column: continuation:36 - │ ├── inverted-expr - │ │ └── t1.j:24 @> t2.j:3 - │ ├── fd: (1)-->(2-4), (22)-->(23,36) - │ ├── inner-join (cross) - │ │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 "inverted_join_const_col_@8":21!null - │ │ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more) - │ │ ├── fd: (1)-->(2-4) - │ │ ├── scan json_arr2 [as=t2] - │ │ │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 - │ │ │ ├── key: (1) - │ │ │ └── fd: (1)-->(2-4) - │ │ ├── values - │ │ │ ├── columns: "inverted_join_const_col_@8":21!null - │ │ │ ├── cardinality: [2 - 2] - │ │ │ ├── (3,) - │ │ │ └── (4,) - │ │ └── filters (true) - │ └── filters (true) - └── filters - └── t1.j:9 @> t2.j:3 [outer=(3,9), immutable] + └── distinct-on + ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 + ├── grouping columns: t2.k:1!null + ├── immutable + ├── key: (1) + ├── fd: (1)-->(2-4) + ├── inner-join (lookup json_arr1 [as=t1]) + │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 i:8!null t1.j:9 + │ ├── key columns: [22] = [7] + │ ├── lookup columns are key + │ ├── immutable + │ ├── fd: (1)-->(2-4) + │ ├── inner-join (inverted json_arr1@j_idx [as=t1]) + │ │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 t1.k:22!null i:23!null + │ │ ├── prefix key columns: [21] = [23] + │ │ ├── inverted-expr + │ │ │ └── t1.j:24 @> t2.j:3 + │ │ ├── fd: (1)-->(2-4), (22)-->(23) + │ │ ├── inner-join (cross) + │ │ │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 "inverted_join_const_col_@8":21!null + │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more) + │ │ │ ├── fd: (1)-->(2-4) + │ │ │ ├── scan json_arr2 [as=t2] + │ │ │ │ ├── columns: t2.k:1!null l:2 t2.j:3 t2.a:4 + │ │ │ │ ├── key: (1) + │ │ │ │ └── fd: (1)-->(2-4) + │ │ │ ├── values + │ │ │ │ ├── columns: "inverted_join_const_col_@8":21!null + │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ ├── (3,) + │ │ │ │ └── (4,) + │ │ │ └── filters (true) + │ │ └── filters (true) + │ └── filters + │ └── t1.j:9 @> t2.j:3 [outer=(3,9), immutable] + └── aggregations + ├── const-agg [as=l:2, outer=(2)] + │ └── l:2 + ├── const-agg [as=t2.j:3, outer=(3)] + │ └── t2.j:3 + └── const-agg [as=t2.a:4, outer=(4)] + └── t2.a:4 # Generate an inverted semi-join on a multi-column inverted index with the # prefix column constrained by an equality constraint. @@ -8968,8 +9011,8 @@ anti-join (lookup json_arr1 [as=t1]) └── filters └── t1.j:9 @> t2.j:3 [outer=(3,9), immutable] -# Regression test for #59615. Ensure that invalid inverted joins are not created -# for left and anti joins. +# Regression test for #59615 and #78681. Ensure that invalid inverted joins are +# not created for left, semi, and anti joins. exec-ddl CREATE TABLE t59615_inv ( x INT NOT NULL CHECK (x in (1, 3)), @@ -8998,6 +9041,31 @@ right-join (cross) └── filters └── y:3 @> column1:1 [outer=(1,3), immutable] +# Disable ConvertSemiToInnerJoin to prevent GenerateInvertedJoins from firing +# for the converted inner join. With the expect-not option, we get added +# assurance that GenerateInvertedJoins is not incorrectly firing for the +# semi-join. +opt disable=ConvertSemiToInnerJoin expect-not=GenerateInvertedJoins +SELECT * FROM (VALUES ('"a"'::jsonb), ('"b"'::jsonb)) AS u(y) WHERE EXISTS ( + SELECT * FROM t59615_inv t WHERE t.y @> u.y +) +---- +semi-join (cross) + ├── columns: y:1!null + ├── cardinality: [0 - 2] + ├── immutable + ├── values + │ ├── columns: column1:1!null + │ ├── cardinality: [2 - 2] + │ ├── ('"a"',) + │ └── ('"b"',) + ├── scan t59615_inv [as=t] + │ ├── columns: y:3 + │ └── check constraint expressions + │ └── x:2 IN (1, 3) [outer=(2), constraints=(/2: [/1 - /1] [/3 - /3]; tight)] + └── filters + └── y:3 @> column1:1 [outer=(1,3), immutable] + opt expect-not=GenerateInvertedJoins SELECT * FROM (VALUES ('"a"'::jsonb), ('"b"'::jsonb)) AS u(y) WHERE NOT EXISTS ( SELECT * FROM t59615_inv t WHERE t.y @> u.y