diff --git a/enginetest/queries/integration_plans.go b/enginetest/queries/integration_plans.go index 216f491b24..fcf0847649 100644 --- a/enginetest/queries/integration_plans.go +++ b/enginetest/queries/integration_plans.go @@ -2889,7 +2889,7 @@ WHERE " │ ├─ columns: [s7byt.SSHPJ, s7byt.SFJ6L]\n" + " │ └─ Filter\n" + " │ ├─ we72e.SSHPJ IS NULL\n" + - " │ └─ LeftOuterLookupJoin (estimated cost=405.900 rows=124)\n" + + " │ └─ LeftOuterLookupJoin (estimated cost=330.000 rows=100)\n" + " │ ├─ SubqueryAlias\n" + " │ │ ├─ name: s7byt\n" + " │ │ ├─ outerVisibility: true\n" + @@ -2956,7 +2956,7 @@ WHERE " │ ├─ columns: [s7byt.SSHPJ, s7byt.SFJ6L]\n" + " │ └─ Filter\n" + " │ ├─ we72e.SSHPJ IS NULL\n" + - " │ └─ LeftOuterLookupJoin (estimated cost=405.900 rows=124)\n" + + " │ └─ LeftOuterLookupJoin (estimated cost=330.000 rows=100)\n" + " │ ├─ SubqueryAlias\n" + " │ │ ├─ name: s7byt\n" + " │ │ ├─ outerVisibility: true\n" + @@ -6678,7 +6678,7 @@ WHERE ExpectedEstimates: "Project\n" + " ├─ columns: [athcu.T4IBQ as T4IBQ, athcu.TW55N as TW55N, CASE WHEN fc.OZTQF IS NULL THEN 0 WHEN (athcu.SJ5DU IN ('log', 'com', 'ex')) THEN 0 WHEN (athcu.SOWRY = 'CRZ2X') THEN 0 WHEN (athcu.SOWRY = 'z') THEN fc.OZTQF WHEN (athcu.SOWRY = 'o') THEN (fc.OZTQF - 1) END as OZTQF]\n" + " └─ Sort(athcu.YYKXN ASC)\n" + - " └─ LeftOuterLookupJoin (estimated cost=401.300 rows=125)\n" + + " └─ LeftOuterLookupJoin (estimated cost=330.000 rows=100)\n" + " ├─ SubqueryAlias\n" + " │ ├─ name: athcu\n" + " │ ├─ outerVisibility: false\n" + @@ -6750,7 +6750,7 @@ WHERE ExpectedAnalysis: "Project\n" + " ├─ columns: [athcu.T4IBQ as T4IBQ, athcu.TW55N as TW55N, CASE WHEN fc.OZTQF IS NULL THEN 0 WHEN (athcu.SJ5DU IN ('log', 'com', 'ex')) THEN 0 WHEN (athcu.SOWRY = 'CRZ2X') THEN 0 WHEN (athcu.SOWRY = 'z') THEN fc.OZTQF WHEN (athcu.SOWRY = 'o') THEN (fc.OZTQF - 1) END as OZTQF]\n" + " └─ Sort(athcu.YYKXN ASC)\n" + - " └─ LeftOuterLookupJoin (estimated cost=401.300 rows=125) (actual rows=0 loops=1)\n" + + " └─ LeftOuterLookupJoin (estimated cost=330.000 rows=100) (actual rows=0 loops=1)\n" + " ├─ SubqueryAlias\n" + " │ ├─ name: athcu\n" + " │ ├─ outerVisibility: false\n" + @@ -7363,11 +7363,11 @@ WHERE " ├─ columns: [bs.T4IBQ as T4IBQ, pa.DZLIM as ECUWU, pga.DZLIM as GSTQA, pog.B5OUF, fc.OZTQF, f26zw.YHYLK, nd.TW55N as TW55N]\n" + " └─ Filter\n" + " ├─ (ms.D237E = 1)\n" + - " └─ LeftOuterHashJoin (estimated cost=174.120 rows=156)\n" + + " └─ LeftOuterHashJoin (estimated cost=142.500 rows=125)\n" + " ├─ (nd.HPCMS = nma.id)\n" + - " ├─ LeftOuterHashJoin (estimated cost=459.120 rows=156)\n" + + " ├─ LeftOuterHashJoin (estimated cost=427.500 rows=125)\n" + " │ ├─ ((f26zw.T4IBQ = bs.T4IBQ) AND (f26zw.BRQP2 = nd.id))\n" + - " │ ├─ LeftOuterLookupJoin (estimated cost=497.600 rows=156)\n" + + " │ ├─ LeftOuterLookupJoin (estimated cost=412.500 rows=125)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=14862.250 rows=125)\n" + " │ │ │ ├─ (ms.GXLUB = bs.id)\n" + " │ │ │ ├─ SubqueryAlias\n" + @@ -7573,11 +7573,11 @@ WHERE " ├─ columns: [bs.T4IBQ as T4IBQ, pa.DZLIM as ECUWU, pga.DZLIM as GSTQA, pog.B5OUF, fc.OZTQF, f26zw.YHYLK, nd.TW55N as TW55N]\n" + " └─ Filter\n" + " ├─ (ms.D237E = 1)\n" + - " └─ LeftOuterHashJoin (estimated cost=174.120 rows=156) (actual rows=0 loops=1)\n" + + " └─ LeftOuterHashJoin (estimated cost=142.500 rows=125) (actual rows=0 loops=1)\n" + " ├─ (nd.HPCMS = nma.id)\n" + - " ├─ LeftOuterHashJoin (estimated cost=459.120 rows=156) (actual rows=0 loops=1)\n" + + " ├─ LeftOuterHashJoin (estimated cost=427.500 rows=125) (actual rows=0 loops=1)\n" + " │ ├─ ((f26zw.T4IBQ = bs.T4IBQ) AND (f26zw.BRQP2 = nd.id))\n" + - " │ ├─ LeftOuterLookupJoin (estimated cost=497.600 rows=156) (actual rows=0 loops=1)\n" + + " │ ├─ LeftOuterLookupJoin (estimated cost=412.500 rows=125) (actual rows=0 loops=1)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=14862.250 rows=125) (actual rows=0 loops=1)\n" + " │ │ │ ├─ (ms.GXLUB = bs.id)\n" + " │ │ │ ├─ SubqueryAlias\n" + @@ -8288,11 +8288,11 @@ WHERE " ├─ columns: [bs.T4IBQ as T4IBQ, pa.DZLIM as ECUWU, pga.DZLIM as GSTQA, pog.B5OUF, fc.OZTQF, f26zw.YHYLK, nd.TW55N as TW55N]\n" + " └─ Filter\n" + " ├─ (ms.D237E = 1)\n" + - " └─ LeftOuterHashJoin (estimated cost=174.120 rows=156)\n" + + " └─ LeftOuterHashJoin (estimated cost=142.500 rows=125)\n" + " ├─ (nd.HPCMS = nma.id)\n" + - " ├─ LeftOuterHashJoin (estimated cost=459.120 rows=156)\n" + + " ├─ LeftOuterHashJoin (estimated cost=427.500 rows=125)\n" + " │ ├─ ((f26zw.T4IBQ = bs.T4IBQ) AND (f26zw.BRQP2 = nd.id))\n" + - " │ ├─ LeftOuterLookupJoin (estimated cost=497.600 rows=156)\n" + + " │ ├─ LeftOuterLookupJoin (estimated cost=412.500 rows=125)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=14862.250 rows=125)\n" + " │ │ │ ├─ (ms.GXLUB = bs.id)\n" + " │ │ │ ├─ SubqueryAlias\n" + @@ -8499,11 +8499,11 @@ WHERE " ├─ columns: [bs.T4IBQ as T4IBQ, pa.DZLIM as ECUWU, pga.DZLIM as GSTQA, pog.B5OUF, fc.OZTQF, f26zw.YHYLK, nd.TW55N as TW55N]\n" + " └─ Filter\n" + " ├─ (ms.D237E = 1)\n" + - " └─ LeftOuterHashJoin (estimated cost=174.120 rows=156) (actual rows=0 loops=1)\n" + + " └─ LeftOuterHashJoin (estimated cost=142.500 rows=125) (actual rows=0 loops=1)\n" + " ├─ (nd.HPCMS = nma.id)\n" + - " ├─ LeftOuterHashJoin (estimated cost=459.120 rows=156) (actual rows=0 loops=1)\n" + + " ├─ LeftOuterHashJoin (estimated cost=427.500 rows=125) (actual rows=0 loops=1)\n" + " │ ├─ ((f26zw.T4IBQ = bs.T4IBQ) AND (f26zw.BRQP2 = nd.id))\n" + - " │ ├─ LeftOuterLookupJoin (estimated cost=497.600 rows=156) (actual rows=0 loops=1)\n" + + " │ ├─ LeftOuterLookupJoin (estimated cost=412.500 rows=125) (actual rows=0 loops=1)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=14862.250 rows=125) (actual rows=0 loops=1)\n" + " │ │ │ ├─ (ms.GXLUB = bs.id)\n" + " │ │ │ ├─ SubqueryAlias\n" + @@ -17013,7 +17013,7 @@ ORDER BY LUEVY`, " │ │ ├─ columns: [luevy xqdyt]\n" + " │ │ └─ keys: nd.id\n" + " │ │ as I3L5A, nd.ETAQ7 as FUG6J, nd.A75X7 as NF5AM, nd.FSK67 as FRCVC]\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=3923.920 rows=3842)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=3923.890 rows=3842)\n" + " │ ├─ cmp: (nd.HPCMS = nma.id)\n" + " │ ├─ TableAlias(nd)\n" + " │ │ └─ IndexedTableAccess(E2I7U)\n" + @@ -17071,7 +17071,7 @@ ORDER BY LUEVY`, " │ │ ├─ columns: [luevy xqdyt]\n" + " │ │ └─ keys: nd.id\n" + " │ │ as I3L5A, nd.ETAQ7 as FUG6J, nd.A75X7 as NF5AM, nd.FSK67 as FRCVC]\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=3923.920 rows=3842) (actual rows=0 loops=1)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=3923.890 rows=3842) (actual rows=0 loops=1)\n" + " │ ├─ cmp: (nd.HPCMS = nma.id)\n" + " │ ├─ TableAlias(nd)\n" + " │ │ └─ IndexedTableAccess(E2I7U)\n" + @@ -17226,7 +17226,7 @@ ORDER BY sn.id ASC`, " ├─ (sn.A7XO2 = it.id)\n" + " ├─ LeftOuterHashJoin (estimated cost=23575.260 rows=11813)\n" + " │ ├─ (sn.FFTBJ = lsm32.id)\n" + - " │ ├─ LeftOuterMergeJoin (estimated cost=15929.710 rows=11813)\n" + + " │ ├─ LeftOuterMergeJoin (estimated cost=15929.680 rows=11813)\n" + " │ │ ├─ cmp: (sn.BRQP2 = tvqg4.id)\n" + " │ │ ├─ TableAlias(sn)\n" + " │ │ │ └─ IndexedTableAccess(NOXN3)\n" + @@ -17260,7 +17260,7 @@ ORDER BY sn.id ASC`, " ├─ (sn.A7XO2 = it.id)\n" + " ├─ LeftOuterHashJoin (estimated cost=23575.260 rows=11813) (actual rows=0 loops=1)\n" + " │ ├─ (sn.FFTBJ = lsm32.id)\n" + - " │ ├─ LeftOuterMergeJoin (estimated cost=15929.710 rows=11813) (actual rows=0 loops=1)\n" + + " │ ├─ LeftOuterMergeJoin (estimated cost=15929.680 rows=11813) (actual rows=0 loops=1)\n" + " │ │ ├─ cmp: (sn.BRQP2 = tvqg4.id)\n" + " │ │ ├─ TableAlias(sn)\n" + " │ │ │ └─ IndexedTableAccess(NOXN3)\n" + @@ -17455,7 +17455,7 @@ ORDER BY rn.id ASC`, " │ │ ├─ (jgt2h.BRQP2 = sdllr.id)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=107544.840 rows=70692)\n" + " │ │ │ ├─ (rn.HHVLX = ayfcd.id)\n" + - " │ │ │ ├─ LeftOuterMergeJoin (estimated cost=84037.000 rows=70692)\n" + + " │ │ │ ├─ LeftOuterMergeJoin (estimated cost=84036.970 rows=70692)\n" + " │ │ │ │ ├─ cmp: (rn.WNUNU = jgt2h.id)\n" + " │ │ │ │ ├─ TableAlias(rn)\n" + " │ │ │ │ │ └─ IndexedTableAccess(QYWQD)\n" + @@ -17507,7 +17507,7 @@ ORDER BY rn.id ASC`, " │ │ ├─ (jgt2h.BRQP2 = sdllr.id)\n" + " │ │ ├─ LeftOuterHashJoin (estimated cost=107544.840 rows=70692) (actual rows=0 loops=1)\n" + " │ │ │ ├─ (rn.HHVLX = ayfcd.id)\n" + - " │ │ │ ├─ LeftOuterMergeJoin (estimated cost=84037.000 rows=70692) (actual rows=0 loops=1)\n" + + " │ │ │ ├─ LeftOuterMergeJoin (estimated cost=84036.970 rows=70692) (actual rows=0 loops=1)\n" + " │ │ │ │ ├─ cmp: (rn.WNUNU = jgt2h.id)\n" + " │ │ │ │ ├─ TableAlias(rn)\n" + " │ │ │ │ │ └─ IndexedTableAccess(QYWQD)\n" + diff --git a/enginetest/queries/query_plans.go b/enginetest/queries/query_plans.go index 635a20a9bd..19dc236bc2 100644 --- a/enginetest/queries/query_plans.go +++ b/enginetest/queries/query_plans.go @@ -520,7 +520,7 @@ From xy;`, " │ ├─ columns: [xy.x, xy.y]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ ├─ cmp: (xy.x = uv.u)\n" + " │ ├─ IndexedTableAccess(xy)\n" + " │ │ ├─ index: [xy.x]\n" + @@ -541,7 +541,7 @@ From xy;`, " │ ├─ columns: [xy.x, xy.y]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ ├─ cmp: (xy.x = uv.u)\n" + " │ ├─ IndexedTableAccess(xy)\n" + " │ │ ├─ index: [xy.x]\n" + @@ -565,7 +565,7 @@ From xy;`, " │ ├─ columns: [xy.x, xy.y]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ ├─ cmp: (xy.x = uv.u)\n" + " │ ├─ IndexedTableAccess(xy)\n" + " │ │ ├─ index: [xy.x]\n" + @@ -586,7 +586,7 @@ From xy;`, " │ ├─ columns: [xy.x, xy.y]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ ├─ cmp: (xy.x = uv.u)\n" + " │ ├─ IndexedTableAccess(xy)\n" + " │ │ ├─ index: [xy.x]\n" + @@ -5407,7 +5407,7 @@ inner join xy on a = x;`, " │ ├─ columns: [ab.a, ab.b, uv.u, uv.v]\n" + " │ └─ HashJoin (estimated cost=1032.000 rows=1000)\n" + " │ ├─ (uv.u = pq.p)\n" + - " │ ├─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ ├─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ │ ├─ cmp: (ab.a = uv.u)\n" + " │ │ ├─ IndexedTableAccess(ab)\n" + " │ │ │ ├─ index: [ab.a]\n" + @@ -5441,7 +5441,7 @@ inner join xy on a = x;`, " │ ├─ columns: [ab.a, ab.b, uv.u, uv.v]\n" + " │ └─ HashJoin (estimated cost=1032.000 rows=1000) (actual rows=4 loops=1)\n" + " │ ├─ (uv.u = pq.p)\n" + - " │ ├─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000) (actual rows=4 loops=1)\n" + + " │ ├─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000) (actual rows=4 loops=1)\n" + " │ │ ├─ cmp: (ab.a = uv.u)\n" + " │ │ ├─ IndexedTableAccess(ab)\n" + " │ │ │ ├─ index: [ab.a]\n" + @@ -5513,7 +5513,7 @@ where exists "", ExpectedEstimates: "Project\n" + " ├─ columns: [ab.a, ab.b]\n" + - " └─ LookupJoin (estimated cost=16.500 rows=5)\n" + + " └─ LookupJoin (estimated cost=13.200 rows=4)\n" + " ├─ (ab.a = uv.u)\n" + " ├─ OrderedDistinct\n" + " │ └─ Project\n" + @@ -5534,7 +5534,7 @@ where exists "", ExpectedAnalysis: "Project\n" + " ├─ columns: [ab.a, ab.b]\n" + - " └─ LookupJoin (estimated cost=16.500 rows=5) (actual rows=4 loops=1)\n" + + " └─ LookupJoin (estimated cost=13.200 rows=4) (actual rows=4 loops=1)\n" + " ├─ (ab.a = uv.u)\n" + " ├─ OrderedDistinct\n" + " │ └─ Project\n" + @@ -5628,7 +5628,7 @@ where exists (select * from pq where a = p) " │ ├─ columns: [ab.a, ab.b]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000)\n" + " │ ├─ cmp: (ab.a = uv.u)\n" + " │ ├─ IndexedTableAccess(ab)\n" + " │ │ ├─ index: [ab.a]\n" + @@ -5664,7 +5664,7 @@ where exists (select * from pq where a = p) " │ ├─ columns: [ab.a, ab.b]\n" + " │ └─ Filter\n" + " │ ├─ uv.u IS NULL\n" + - " │ └─ LeftOuterMergeJoin (estimated cost=1024.070 rows=1000) (actual rows=4 loops=1)\n" + + " │ └─ LeftOuterMergeJoin (estimated cost=1024.040 rows=1000) (actual rows=4 loops=1)\n" + " │ ├─ cmp: (ab.a = uv.u)\n" + " │ ├─ IndexedTableAccess(ab)\n" + " │ │ ├─ index: [ab.a]\n" + @@ -5951,7 +5951,7 @@ inner join pq on true " │ │ ├─ columns: [ab.a, ab.b]\n" + " │ │ └─ Filter\n" + " │ │ ├─ xy.x IS NULL\n" + - " │ │ └─ LeftOuterMergeJoin (estimated cost=2032.530 rows=1250)\n" + + " │ │ └─ LeftOuterMergeJoin (estimated cost=2030.000 rows=1000)\n" + " │ │ ├─ cmp: (ab.a = xy.x)\n" + " │ │ ├─ IndexedTableAccess(ab)\n" + " │ │ │ ├─ index: [ab.a]\n" + @@ -5996,7 +5996,7 @@ inner join pq on true " │ │ ├─ columns: [ab.a, ab.b]\n" + " │ │ └─ Filter\n" + " │ │ ├─ xy.x IS NULL\n" + - " │ │ └─ LeftOuterMergeJoin (estimated cost=2032.530 rows=1250) (actual rows=4 loops=1)\n" + + " │ │ └─ LeftOuterMergeJoin (estimated cost=2030.000 rows=1000) (actual rows=4 loops=1)\n" + " │ │ ├─ cmp: (ab.a = xy.x)\n" + " │ │ ├─ IndexedTableAccess(ab)\n" + " │ │ │ ├─ index: [ab.a]\n" + @@ -6091,8 +6091,8 @@ inner join pq on true " │ └─ columns: [i s]\n" + " └─ TableAlias(b)\n" + " └─ IndexedTableAccess(mytable)\n" + - " ├─ index: [mytable.i,mytable.s]\n" + - " ├─ static: [{[NULL, ∞), [NULL, ∞)}]\n" + + " ├─ index: [mytable.i]\n" + + " ├─ static: [{[NULL, ∞)}]\n" + " ├─ colSet: (3,4)\n" + " ├─ tableId: 2\n" + " └─ Table\n" + @@ -6105,7 +6105,7 @@ inner join pq on true " ├─ columns: [mytable.i, mytable.s]\n" + " └─ Filter\n" + " ├─ b.i IS NULL\n" + - " └─ LeftOuterMergeJoin (estimated cost=6.120 rows=3)\n" + + " └─ LeftOuterMergeJoin (estimated cost=6.090 rows=3)\n" + " ├─ cmp: (a.i = b.i)\n" + " ├─ TableAlias(a)\n" + " │ └─ IndexedTableAccess(mytable)\n" + @@ -6113,8 +6113,8 @@ inner join pq on true " │ └─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " └─ TableAlias(b)\n" + " └─ IndexedTableAccess(mytable)\n" + - " ├─ index: [mytable.i,mytable.s]\n" + - " ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + + " ├─ index: [mytable.i]\n" + + " ├─ filters: [{[NULL, ∞)}]\n" + " └─ columns: [i]\n" + "", ExpectedAnalysis: "Project\n" + @@ -6123,7 +6123,7 @@ inner join pq on true " ├─ columns: [mytable.i, mytable.s]\n" + " └─ Filter\n" + " ├─ b.i IS NULL\n" + - " └─ LeftOuterMergeJoin (estimated cost=6.120 rows=3) (actual rows=3 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=6.090 rows=3) (actual rows=3 loops=1)\n" + " ├─ cmp: (a.i = b.i)\n" + " ├─ TableAlias(a)\n" + " │ └─ IndexedTableAccess(mytable)\n" + @@ -6131,8 +6131,8 @@ inner join pq on true " │ └─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " └─ TableAlias(b)\n" + " └─ IndexedTableAccess(mytable)\n" + - " ├─ index: [mytable.i,mytable.s]\n" + - " ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + + " ├─ index: [mytable.i]\n" + + " ├─ filters: [{[NULL, ∞)}]\n" + " └─ columns: [i]\n" + "", }, @@ -11718,42 +11718,41 @@ inner join pq on true Query: `SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON one_pk.pk <=> two_pk.pk1 AND one_pk.pk = two_pk.pk2`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null, two_pk.pk1:1!null, two_pk.pk2:2!null]\n" + - " └─ LeftOuterJoin\n" + - " ├─ AND\n" + - " │ ├─ (one_pk.pk:0!null <=> two_pk.pk1:1!null)\n" + - " │ └─ Eq\n" + - " │ ├─ one_pk.pk:0!null\n" + - " │ └─ two_pk.pk2:2!null\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ ProcessTable\n" + " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ ProcessTable\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ keys: [one_pk.pk:0!null one_pk.pk:0!null]\n" + + " ├─ colSet: (7-13)\n" + + " ├─ tableId: 2\n" + " └─ Table\n" + " ├─ name: two_pk\n" + " └─ columns: [pk1 pk2]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5)\n" + - " ├─ ((one_pk.pk <=> two_pk.pk1) AND (one_pk.pk = two_pk.pk2))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4)\n" + " ├─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: one_pk.pk, one_pk.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5) (actual rows=4 loops=1)\n" + - " ├─ ((one_pk.pk <=> two_pk.pk1) AND (one_pk.pk = two_pk.pk2))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4) (actual rows=4 loops=1)\n" + " ├─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: one_pk.pk, one_pk.pk\n" + "", }, { @@ -11784,7 +11783,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.160 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + " ├─ cmp: (one_pk.pk = two_pk.pk1)\n" + " ├─ sel: (one_pk.pk <=> two_pk.pk2)\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -11798,7 +11797,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.160 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = two_pk.pk1)\n" + " ├─ sel: (one_pk.pk <=> two_pk.pk2)\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -11815,40 +11814,41 @@ inner join pq on true Query: `SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON one_pk.pk <=> two_pk.pk1 AND one_pk.pk <=> two_pk.pk2`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null, two_pk.pk1:1!null, two_pk.pk2:2!null]\n" + - " └─ LeftOuterJoin\n" + - " ├─ AND\n" + - " │ ├─ (one_pk.pk:0!null <=> two_pk.pk1:1!null)\n" + - " │ └─ (one_pk.pk:0!null <=> two_pk.pk2:2!null)\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ ProcessTable\n" + " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ ProcessTable\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ keys: [one_pk.pk:0!null one_pk.pk:0!null]\n" + + " ├─ colSet: (7-13)\n" + + " ├─ tableId: 2\n" + " └─ Table\n" + " ├─ name: two_pk\n" + " └─ columns: [pk1 pk2]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5)\n" + - " ├─ ((one_pk.pk <=> two_pk.pk1) AND (one_pk.pk <=> two_pk.pk2))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4)\n" + " ├─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: one_pk.pk, one_pk.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5) (actual rows=4 loops=1)\n" + - " ├─ ((one_pk.pk <=> two_pk.pk1) AND (one_pk.pk <=> two_pk.pk2))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4) (actual rows=4 loops=1)\n" + " ├─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk]\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: one_pk.pk, one_pk.pk\n" + "", }, { @@ -11857,8 +11857,11 @@ inner join pq on true " ├─ columns: [one_pk.pk:2!null, two_pk.pk1:0!null, two_pk.pk2:1!null]\n" + " └─ LeftOuterMergeJoin\n" + " ├─ cmp: Eq\n" + - " │ ├─ TUPLE(two_pk.pk1:0!null, two_pk.pk2:1!null)\n" + - " │ └─ TUPLE(one_pk.pk:2!null, one_pk.pk:2!null)\n" + + " │ ├─ two_pk.pk1:0!null\n" + + " │ └─ one_pk.pk:2!null\n" + + " ├─ sel: Eq\n" + + " │ ├─ one_pk.pk:2!null\n" + + " │ └─ two_pk.pk2:1!null\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ static: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -11878,8 +11881,9 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5)\n" + - " ├─ cmp: ((two_pk.pk1, two_pk.pk2) = (one_pk.pk, one_pk.pk))\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + + " ├─ cmp: (two_pk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -11891,8 +11895,9 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5) (actual rows=4 loops=1)\n" + - " ├─ cmp: ((two_pk.pk1, two_pk.pk2) = (one_pk.pk, one_pk.pk))\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + + " ├─ cmp: (two_pk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -12716,14 +12721,7 @@ inner join pq on true LEFT JOIN two_pk tpk2 ON tpk2.pk1=TPK.pk2 AND TPK2.pk2=tpk.pk1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null]\n" + - " └─ LeftOuterJoin\n" + - " ├─ AND\n" + - " │ ├─ Eq\n" + - " │ │ ├─ tpk2.pk1:3!null\n" + - " │ │ └─ tpk.pk2:2!null\n" + - " │ └─ Eq\n" + - " │ ├─ tpk2.pk2:4!null\n" + - " │ └─ tpk.pk1:1!null\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ MergeJoin\n" + " │ ├─ cmp: Eq\n" + " │ │ ├─ one_pk.pk:0!null\n" + @@ -12749,15 +12747,18 @@ inner join pq on true " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ ProcessTable\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ keys: [tpk.pk2:2!null tpk.pk1:1!null]\n" + + " ├─ colSet: (14-20)\n" + + " ├─ tableId: 3\n" + " └─ Table\n" + " ├─ name: two_pk\n" + " └─ columns: [pk1 pk2]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4)\n" + " ├─ MergeJoin (estimated cost=8.120 rows=4)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + @@ -12771,14 +12772,14 @@ inner join pq on true " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5) (actual rows=2 loops=1)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4) (actual rows=2 loops=1)\n" + " ├─ MergeJoin (estimated cost=8.120 rows=4) (actual rows=2 loops=1)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + @@ -12792,9 +12793,10 @@ inner join pq on true " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", }, { @@ -12903,14 +12905,7 @@ inner join pq on true LEFT JOIN two_pk tpk2 ON tpk2.pk1=TPK.pk2 AND TPK2.pk2=tpk.pk1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null]\n" + - " └─ LeftOuterHashJoin\n" + - " ├─ AND\n" + - " │ ├─ Eq\n" + - " │ │ ├─ tpk2.pk1:3!null\n" + - " │ │ └─ tpk.pk2:2!null\n" + - " │ └─ Eq\n" + - " │ ├─ tpk2.pk2:4!null\n" + - " │ └─ tpk.pk1:1!null\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ LeftOuterMergeJoin\n" + " │ ├─ cmp: Eq\n" + " │ │ ├─ one_pk.pk:0!null\n" + @@ -12935,20 +12930,20 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: TUPLE(tpk.pk2:2!null, tpk.pk1:1!null)\n" + - " ├─ right-key: TUPLE(tpk2.pk1:0!null, tpk2.pk2:1!null)\n" + - " └─ TableAlias(tpk2)\n" + - " └─ ProcessTable\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ TableAlias(tpk2)\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ keys: [tpk.pk2:2!null tpk.pk1:1!null]\n" + + " ├─ colSet: (14-20)\n" + + " ├─ tableId: 3\n" + + " └─ Table\n" + + " ├─ name: two_pk\n" + + " └─ columns: [pk1 pk2]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + - " ├─ LeftOuterMergeJoin (estimated cost=8.160 rows=5)\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4)\n" + + " ├─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + " │ ├─ IndexedTableAccess(one_pk)\n" + @@ -12960,19 +12955,16 @@ inner join pq on true " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (tpk.pk2, tpk.pk1)\n" + - " ├─ right-key: (tpk2.pk1, tpk2.pk2)\n" + - " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ TableAlias(tpk2)\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5) (actual rows=4 loops=1)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + - " ├─ LeftOuterMergeJoin (estimated cost=8.160 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4) (actual rows=4 loops=1)\n" + + " ├─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + " │ ├─ IndexedTableAccess(one_pk)\n" + @@ -12984,13 +12976,11 @@ inner join pq on true " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (tpk.pk2, tpk.pk1)\n" + - " ├─ right-key: (tpk2.pk1, tpk2.pk2)\n" + - " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ TableAlias(tpk2)\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", }, { @@ -13036,8 +13026,8 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LookupJoin (estimated cost=16.500 rows=5)\n" + - " ├─ LeftOuterMergeJoin (estimated cost=8.160 rows=5)\n" + + " └─ LookupJoin (estimated cost=13.200 rows=4)\n" + + " ├─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + " │ ├─ IndexedTableAccess(one_pk)\n" + @@ -13057,8 +13047,8 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LookupJoin (estimated cost=16.500 rows=5) (actual rows=2 loops=1)\n" + - " ├─ LeftOuterMergeJoin (estimated cost=8.160 rows=5) (actual rows=4 loops=1)\n" + + " └─ LookupJoin (estimated cost=13.200 rows=4) (actual rows=2 loops=1)\n" + + " ├─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + " │ ├─ IndexedTableAccess(one_pk)\n" + @@ -13083,14 +13073,7 @@ inner join pq on true LEFT JOIN two_pk tpk2 ON tpk2.pk1=TPK.pk2 AND TPK2.pk2=tpk.pk1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null]\n" + - " └─ LeftOuterJoin\n" + - " ├─ AND\n" + - " │ ├─ Eq\n" + - " │ │ ├─ tpk2.pk1:3!null\n" + - " │ │ └─ tpk.pk2:2!null\n" + - " │ └─ Eq\n" + - " │ ├─ tpk2.pk2:4!null\n" + - " │ └─ tpk.pk1:1!null\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ MergeJoin\n" + " │ ├─ cmp: Eq\n" + " │ │ ├─ one_pk.pk:0!null\n" + @@ -13116,15 +13099,18 @@ inner join pq on true " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ ProcessTable\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ keys: [tpk.pk2:2!null tpk.pk1:1!null]\n" + + " ├─ colSet: (14-20)\n" + + " ├─ tableId: 3\n" + " └─ Table\n" + " ├─ name: two_pk\n" + " └─ columns: [pk1 pk2]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4)\n" + " ├─ MergeJoin (estimated cost=8.120 rows=4)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + @@ -13138,14 +13124,14 @@ inner join pq on true " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterJoin (estimated cost=15.160 rows=5) (actual rows=2 loops=1)\n" + - " ├─ ((tpk2.pk1 = tpk.pk2) AND (tpk2.pk2 = tpk.pk1))\n" + + " └─ LeftOuterLookupJoin (estimated cost=13.200 rows=4) (actual rows=2 loops=1)\n" + " ├─ MergeJoin (estimated cost=8.120 rows=4) (actual rows=2 loops=1)\n" + " │ ├─ cmp: (one_pk.pk = tpk.pk1)\n" + " │ ├─ sel: (one_pk.pk = tpk.pk2)\n" + @@ -13159,9 +13145,10 @@ inner join pq on true " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + " │ └─ columns: [pk1 pk2]\n" + " └─ TableAlias(tpk2)\n" + - " └─ Table\n" + - " ├─ name: two_pk\n" + - " └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(two_pk)\n" + + " ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " ├─ columns: [pk1 pk2]\n" + + " └─ keys: tpk.pk2, tpk.pk1\n" + "", }, { @@ -13170,7 +13157,7 @@ inner join pq on true RIGHT JOIN two_pk tpk2 ON tpk.pk1=TPk2.pk2 AND tpk.pk2=TPK2.pk1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:4!null]\n" + - " └─ LeftOuterHashJoin\n" + + " └─ LeftOuterJoin\n" + " ├─ AND\n" + " │ ├─ Eq\n" + " │ │ ├─ tpk.pk1:2!null\n" + @@ -13183,76 +13170,72 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: TUPLE(tpk2.pk2:1!null, tpk2.pk1:0!null)\n" + - " ├─ right-key: TUPLE(tpk.pk1:0!null, tpk.pk2:1!null)\n" + - " └─ LeftOuterMergeJoin\n" + - " ├─ cmp: Eq\n" + - " │ ├─ TUPLE(tpk.pk1:2!null, tpk.pk2:3!null)\n" + - " │ └─ TUPLE(one_pk.pk:4!null, one_pk.pk:4!null)\n" + - " ├─ TableAlias(tpk)\n" + - " │ └─ IndexedTableAccess(two_pk)\n" + - " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + - " │ ├─ static: [{[NULL, ∞), [NULL, ∞)}]\n" + - " │ ├─ colSet: (7-13)\n" + - " │ ├─ tableId: 2\n" + - " │ └─ Table\n" + - " │ ├─ name: two_pk\n" + - " │ └─ columns: [pk1 pk2]\n" + - " └─ IndexedTableAccess(one_pk)\n" + - " ├─ index: [one_pk.pk]\n" + - " ├─ static: [{[NULL, ∞)}]\n" + - " ├─ colSet: (1-6)\n" + - " ├─ tableId: 1\n" + - " └─ Table\n" + - " ├─ name: one_pk\n" + - " └─ columns: [pk]\n" + + " └─ LeftOuterMergeJoin\n" + + " ├─ cmp: Eq\n" + + " │ ├─ tpk.pk1:2!null\n" + + " │ └─ one_pk.pk:4!null\n" + + " ├─ sel: Eq\n" + + " │ ├─ one_pk.pk:4!null\n" + + " │ └─ tpk.pk2:3!null\n" + + " ├─ TableAlias(tpk)\n" + + " │ └─ IndexedTableAccess(two_pk)\n" + + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " │ ├─ static: [{[NULL, ∞), [NULL, ∞)}]\n" + + " │ ├─ colSet: (7-13)\n" + + " │ ├─ tableId: 2\n" + + " │ └─ Table\n" + + " │ ├─ name: two_pk\n" + + " │ └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ static: [{[NULL, ∞)}]\n" + + " ├─ colSet: (1-6)\n" + + " ├─ tableId: 1\n" + + " └─ Table\n" + + " ├─ name: one_pk\n" + + " └─ columns: [pk]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterHashJoin (estimated cost=19.090 rows=5)\n" + + " └─ LeftOuterJoin (estimated cost=15.160 rows=5)\n" + " ├─ ((tpk.pk1 = tpk2.pk2) AND (tpk.pk2 = tpk2.pk1))\n" + " ├─ TableAlias(tpk2)\n" + " │ └─ Table\n" + " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (tpk2.pk2, tpk2.pk1)\n" + - " ├─ right-key: (tpk.pk1, tpk.pk2)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5)\n" + - " ├─ cmp: ((tpk.pk1, tpk.pk2) = (one_pk.pk, one_pk.pk))\n" + - " ├─ TableAlias(tpk)\n" + - " │ └─ IndexedTableAccess(two_pk)\n" + - " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + - " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + - " │ └─ columns: [pk1 pk2]\n" + - " └─ IndexedTableAccess(one_pk)\n" + - " ├─ index: [one_pk.pk]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [pk]\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + + " ├─ cmp: (tpk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = tpk.pk2)\n" + + " ├─ TableAlias(tpk)\n" + + " │ └─ IndexedTableAccess(two_pk)\n" + + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + + " │ └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ filters: [{[NULL, ∞)}]\n" + + " └─ columns: [pk]\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk]\n" + - " └─ LeftOuterHashJoin (estimated cost=19.090 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterJoin (estimated cost=15.160 rows=5) (actual rows=4 loops=1)\n" + " ├─ ((tpk.pk1 = tpk2.pk2) AND (tpk.pk2 = tpk2.pk1))\n" + " ├─ TableAlias(tpk2)\n" + " │ └─ Table\n" + " │ ├─ name: two_pk\n" + " │ └─ columns: [pk1 pk2]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (tpk2.pk2, tpk2.pk1)\n" + - " ├─ right-key: (tpk.pk1, tpk.pk2)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5) (actual rows=4 loops=1)\n" + - " ├─ cmp: ((tpk.pk1, tpk.pk2) = (one_pk.pk, one_pk.pk))\n" + - " ├─ TableAlias(tpk)\n" + - " │ └─ IndexedTableAccess(two_pk)\n" + - " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + - " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + - " │ └─ columns: [pk1 pk2]\n" + - " └─ IndexedTableAccess(one_pk)\n" + - " ├─ index: [one_pk.pk]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [pk]\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=4)\n" + + " ├─ cmp: (tpk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = tpk.pk2)\n" + + " ├─ TableAlias(tpk)\n" + + " │ └─ IndexedTableAccess(two_pk)\n" + + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + + " │ └─ columns: [pk1 pk2]\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ filters: [{[NULL, ∞)}]\n" + + " └─ columns: [pk]\n" + "", }, { @@ -13387,7 +13370,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13400,7 +13383,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13439,7 +13422,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -13452,7 +13435,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -13515,7 +13498,7 @@ inner join pq on true " └─ HashLookup\n" + " ├─ left-key: ((nt2.i + 1))\n" + " ├─ right-key: (one_pk.pk)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (nt.i = one_pk.pk)\n" + " ├─ TableAlias(nt)\n" + " │ └─ IndexedTableAccess(niltable)\n" + @@ -13538,7 +13521,7 @@ inner join pq on true " └─ HashLookup\n" + " ├─ left-key: ((nt2.i + 1))\n" + " ├─ right-key: (one_pk.pk)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (nt.i = one_pk.pk)\n" + " ├─ TableAlias(nt)\n" + " │ └─ IndexedTableAccess(niltable)\n" + @@ -13580,7 +13563,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ sel: (NOT(niltable.f IS NULL))\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -13594,7 +13577,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ sel: (NOT(niltable.f IS NULL))\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -13637,7 +13620,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ sel: (one_pk.pk > 0)\n" + " ├─ IndexedTableAccess(niltable)\n" + @@ -13651,7 +13634,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ sel: (one_pk.pk > 0)\n" + " ├─ IndexedTableAccess(niltable)\n" + @@ -13696,7 +13679,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (NOT(niltable.f IS NULL))\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13711,7 +13694,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (NOT(niltable.f IS NULL))\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13756,7 +13739,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (niltable.i2 > 1)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13771,7 +13754,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (niltable.i2 > 1)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13816,7 +13799,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (niltable.i > 1)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13831,7 +13814,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (niltable.i > 1)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -13874,7 +13857,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.150 rows=3)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.120 rows=3)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ Filter\n" + " │ ├─ (one_pk.c1 > 10)\n" + @@ -13889,7 +13872,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.150 rows=3) (actual rows=2 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.120 rows=3) (actual rows=2 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ Filter\n" + " │ ├─ (one_pk.c1 > 10)\n" + @@ -13933,7 +13916,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.170 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.140 rows=5)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ Filter\n" + " │ ├─ (NOT(niltable.f IS NULL))\n" + @@ -13948,7 +13931,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.170 rows=5) (actual rows=3 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.140 rows=5) (actual rows=3 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ Filter\n" + " │ ├─ (NOT(niltable.f IS NULL))\n" + @@ -13966,25 +13949,18 @@ inner join pq on true Query: `SELECT pk,i,f FROM one_pk LEFT JOIN niltable ON pk=i WHERE pk > 1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null, niltable.i:1!null, niltable.f:2]\n" + - " └─ LeftOuterMergeJoin\n" + - " ├─ cmp: Eq\n" + - " │ ├─ one_pk.pk:0!null\n" + - " │ └─ niltable.i:1!null\n" + - " ├─ Filter\n" + - " │ ├─ GreaterThan\n" + - " │ │ ├─ one_pk.pk:0!null\n" + - " │ │ └─ 1 (smallint)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ static: [{[NULL, ∞)}]\n" + - " │ ├─ colSet: (1-6)\n" + - " │ ├─ tableId: 1\n" + - " │ └─ Table\n" + - " │ ├─ name: one_pk\n" + - " │ └─ columns: [pk]\n" + + " └─ LeftOuterLookupJoin\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ static: [{(1, ∞)}]\n" + + " │ ├─ colSet: (1-6)\n" + + " │ ├─ tableId: 1\n" + + " │ └─ Table\n" + + " │ ├─ name: one_pk\n" + + " │ └─ columns: [pk]\n" + " └─ IndexedTableAccess(niltable)\n" + " ├─ index: [niltable.i]\n" + - " ├─ static: [{[NULL, ∞)}]\n" + + " ├─ keys: [one_pk.pk:0!null]\n" + " ├─ colSet: (7-10)\n" + " ├─ tableId: 2\n" + " └─ Table\n" + @@ -13993,33 +13969,27 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + - " ├─ cmp: (one_pk.pk = niltable.i)\n" + - " ├─ Filter\n" + - " │ ├─ (one_pk.pk > 1)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ filters: [{[NULL, ∞)}]\n" + - " │ └─ columns: [pk]\n" + + " └─ LeftOuterLookupJoin (estimated cost=6.600 rows=2)\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ filters: [{(1, ∞)}]\n" + + " │ └─ columns: [pk]\n" + " └─ IndexedTableAccess(niltable)\n" + " ├─ index: [niltable.i]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [i f]\n" + + " ├─ columns: [i f]\n" + + " └─ keys: one_pk.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=2 loops=1)\n" + - " ├─ cmp: (one_pk.pk = niltable.i)\n" + - " ├─ Filter\n" + - " │ ├─ (one_pk.pk > 1)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ filters: [{[NULL, ∞)}]\n" + - " │ └─ columns: [pk]\n" + + " └─ LeftOuterLookupJoin (estimated cost=6.600 rows=2) (actual rows=2 loops=1)\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ filters: [{(1, ∞)}]\n" + + " │ └─ columns: [pk]\n" + " └─ IndexedTableAccess(niltable)\n" + " ├─ index: [niltable.i]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [i f]\n" + + " ├─ columns: [i f]\n" + + " └─ keys: one_pk.pk\n" + "", }, { @@ -14103,7 +14073,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (one_pk.pk > 0)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -14118,7 +14088,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (one_pk.pk > 0)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -14975,7 +14945,7 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -14988,7 +14958,7 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -15032,7 +15002,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (NOT(niltable.f IS NULL))\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -15047,7 +15017,7 @@ inner join pq on true " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Filter\n" + " ├─ (NOT(niltable.f IS NULL))\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.180 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.140 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = niltable.i)\n" + " ├─ IndexedTableAccess(one_pk)\n" + " │ ├─ index: [one_pk.pk]\n" + @@ -15063,60 +15033,50 @@ inner join pq on true Query: `SELECT pk,i,f FROM one_pk LEFT JOIN niltable ON pk=i WHERE pk > 1 ORDER BY 1`, ExpectedPlan: "Project\n" + " ├─ columns: [one_pk.pk:0!null, niltable.i:1!null, niltable.f:2]\n" + - " └─ LeftOuterMergeJoin\n" + - " ├─ cmp: Eq\n" + - " │ ├─ one_pk.pk:0!null\n" + - " │ └─ niltable.i:1!null\n" + - " ├─ Filter\n" + - " │ ├─ GreaterThan\n" + - " │ │ ├─ one_pk.pk:0!null\n" + - " │ │ └─ 1 (smallint)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ static: [{[NULL, ∞)}]\n" + - " │ ├─ colSet: (1-6)\n" + - " │ ├─ tableId: 1\n" + - " │ └─ Table\n" + - " │ ├─ name: one_pk\n" + - " │ └─ columns: [pk]\n" + - " └─ IndexedTableAccess(niltable)\n" + - " ├─ index: [niltable.i]\n" + - " ├─ static: [{[NULL, ∞)}]\n" + - " ├─ colSet: (7-10)\n" + - " ├─ tableId: 2\n" + - " └─ Table\n" + - " ├─ name: niltable\n" + - " └─ columns: [i f]\n" + + " └─ Sort(one_pk.pk:0!null ASC nullsFirst)\n" + + " └─ LeftOuterLookupJoin\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ static: [{(1, ∞)}]\n" + + " │ ├─ colSet: (1-6)\n" + + " │ ├─ tableId: 1\n" + + " │ └─ Table\n" + + " │ ├─ name: one_pk\n" + + " │ └─ columns: [pk]\n" + + " └─ IndexedTableAccess(niltable)\n" + + " ├─ index: [niltable.i]\n" + + " ├─ keys: [one_pk.pk:0!null]\n" + + " ├─ colSet: (7-10)\n" + + " ├─ tableId: 2\n" + + " └─ Table\n" + + " ├─ name: niltable\n" + + " └─ columns: [i f]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + - " ├─ cmp: (one_pk.pk = niltable.i)\n" + - " ├─ Filter\n" + - " │ ├─ (one_pk.pk > 1)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ filters: [{[NULL, ∞)}]\n" + - " │ └─ columns: [pk]\n" + - " └─ IndexedTableAccess(niltable)\n" + - " ├─ index: [niltable.i]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [i f]\n" + + " └─ Sort(one_pk.pk ASC)\n" + + " └─ LeftOuterLookupJoin (estimated cost=6.600 rows=2)\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ filters: [{(1, ∞)}]\n" + + " │ └─ columns: [pk]\n" + + " └─ IndexedTableAccess(niltable)\n" + + " ├─ index: [niltable.i]\n" + + " ├─ columns: [i f]\n" + + " └─ keys: one_pk.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=2 loops=1)\n" + - " ├─ cmp: (one_pk.pk = niltable.i)\n" + - " ├─ Filter\n" + - " │ ├─ (one_pk.pk > 1)\n" + - " │ └─ IndexedTableAccess(one_pk)\n" + - " │ ├─ index: [one_pk.pk]\n" + - " │ ├─ filters: [{[NULL, ∞)}]\n" + - " │ └─ columns: [pk]\n" + - " └─ IndexedTableAccess(niltable)\n" + - " ├─ index: [niltable.i]\n" + - " ├─ filters: [{[NULL, ∞)}]\n" + - " └─ columns: [i f]\n" + + " └─ Sort(one_pk.pk ASC)\n" + + " └─ LeftOuterLookupJoin (estimated cost=6.600 rows=2) (actual rows=2 loops=1)\n" + + " ├─ IndexedTableAccess(one_pk)\n" + + " │ ├─ index: [one_pk.pk]\n" + + " │ ├─ filters: [{(1, ∞)}]\n" + + " │ └─ columns: [pk]\n" + + " └─ IndexedTableAccess(niltable)\n" + + " ├─ index: [niltable.i]\n" + + " ├─ columns: [i f]\n" + + " └─ keys: one_pk.pk\n" + "", }, { @@ -15148,7 +15108,7 @@ inner join pq on true ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -15162,7 +15122,7 @@ inner join pq on true ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -15206,7 +15166,7 @@ inner join pq on true ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.170 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.140 rows=5)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ Filter\n" + " │ ├─ (NOT(niltable.f IS NULL))\n" + @@ -15222,7 +15182,7 @@ inner join pq on true ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=9.170 rows=5) (actual rows=3 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=9.140 rows=5) (actual rows=3 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ Filter\n" + " │ ├─ (NOT(niltable.f IS NULL))\n" + @@ -15271,7 +15231,7 @@ inner join pq on true " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + " └─ Filter\n" + " ├─ (one_pk.pk > 0)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -15287,7 +15247,7 @@ inner join pq on true " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + " └─ Filter\n" + " ├─ (one_pk.pk > 0)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ IndexedTableAccess(niltable)\n" + " │ ├─ index: [niltable.i]\n" + @@ -15331,7 +15291,7 @@ inner join pq on true ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ sel: (one_pk.pk > 0)\n" + " ├─ IndexedTableAccess(niltable)\n" + @@ -15346,7 +15306,7 @@ inner join pq on true ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, niltable.i, niltable.f]\n" + " └─ Sort(niltable.i ASC, niltable.f ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=10.190 rows=6) (actual rows=6 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=10.160 rows=6) (actual rows=6 loops=1)\n" + " ├─ cmp: (niltable.i = one_pk.pk)\n" + " ├─ sel: (one_pk.pk > 0)\n" + " ├─ IndexedTableAccess(niltable)\n" + @@ -15531,7 +15491,7 @@ inner join pq on true ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + " └─ Sort(one_pk.pk ASC, two_pk.pk1 ASC, two_pk.pk2 ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.160 rows=5)\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + " ├─ cmp: (one_pk.pk = two_pk.pk1)\n" + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -15546,7 +15506,7 @@ inner join pq on true ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + " └─ Sort(one_pk.pk ASC, two_pk.pk1 ASC, two_pk.pk2 ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.160 rows=5) (actual rows=4 loops=1)\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + " ├─ cmp: (one_pk.pk = two_pk.pk1)\n" + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(one_pk)\n" + @@ -15621,8 +15581,11 @@ inner join pq on true " └─ Sort(one_pk.pk:2!null ASC nullsFirst, two_pk.pk1:0!null ASC nullsFirst, two_pk.pk2:1!null ASC nullsFirst)\n" + " └─ LeftOuterMergeJoin\n" + " ├─ cmp: Eq\n" + - " │ ├─ TUPLE(two_pk.pk1:0!null, two_pk.pk2:1!null)\n" + - " │ └─ TUPLE(one_pk.pk:2!null, one_pk.pk:2!null)\n" + + " │ ├─ two_pk.pk1:0!null\n" + + " │ └─ one_pk.pk:2!null\n" + + " ├─ sel: Eq\n" + + " │ ├─ one_pk.pk:2!null\n" + + " │ └─ two_pk.pk2:1!null\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ static: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -15643,8 +15606,9 @@ inner join pq on true ExpectedEstimates: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + " └─ Sort(one_pk.pk ASC, two_pk.pk1 ASC, two_pk.pk2 ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5)\n" + - " ├─ cmp: ((two_pk.pk1, two_pk.pk2) = (one_pk.pk, one_pk.pk))\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4)\n" + + " ├─ cmp: (two_pk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -15657,8 +15621,9 @@ inner join pq on true ExpectedAnalysis: "Project\n" + " ├─ columns: [one_pk.pk, two_pk.pk1, two_pk.pk2]\n" + " └─ Sort(one_pk.pk ASC, two_pk.pk1 ASC, two_pk.pk2 ASC)\n" + - " └─ LeftOuterMergeJoin (estimated cost=8.150 rows=5) (actual rows=4 loops=1)\n" + - " ├─ cmp: ((two_pk.pk1, two_pk.pk2) = (one_pk.pk, one_pk.pk))\n" + + " └─ LeftOuterMergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + + " ├─ cmp: (two_pk.pk1 = one_pk.pk)\n" + + " ├─ sel: (one_pk.pk = two_pk.pk2)\n" + " ├─ IndexedTableAccess(two_pk)\n" + " │ ├─ index: [two_pk.pk1,two_pk.pk2]\n" + " │ ├─ filters: [{[NULL, ∞), [NULL, ∞)}]\n" + @@ -16859,14 +16824,10 @@ inner join pq on true Query: `SELECT a.* FROM one_pk a CROSS JOIN one_pk c LEFT JOIN one_pk b ON b.pk = c.pk and b.pk = a.pk`, ExpectedPlan: "Project\n" + " ├─ columns: [a.pk:1!null, a.c1:2, a.c2:3, a.c3:4, a.c4:5, a.c5:6]\n" + - " └─ LeftOuterHashJoin\n" + - " ├─ AND\n" + - " │ ├─ Eq\n" + - " │ │ ├─ b.pk:7!null\n" + - " │ │ └─ c.pk:0!null\n" + - " │ └─ Eq\n" + - " │ ├─ b.pk:7!null\n" + - " │ └─ a.pk:1!null\n" + + " └─ LeftOuterLookupJoin\n" + + " ├─ Eq\n" + + " │ ├─ b.pk:7!null\n" + + " │ └─ a.pk:1!null\n" + " ├─ CrossJoin\n" + " │ ├─ TableAlias(c)\n" + " │ │ └─ ProcessTable\n" + @@ -16879,20 +16840,20 @@ inner join pq on true " │ ├─ columns: [pk c1 c2 c3 c4 c5]\n" + " │ ├─ colSet: (1-6)\n" + " │ └─ tableId: 1\n" + - " └─ HashLookup\n" + - " ├─ left-key: TUPLE(c.pk:0!null, a.pk:1!null)\n" + - " ├─ right-key: TUPLE(b.pk:0!null, b.pk:0!null)\n" + - " └─ TableAlias(b)\n" + + " └─ TableAlias(b)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ keys: [c.pk:0!null]\n" + + " ├─ colSet: (13-18)\n" + + " ├─ tableId: 3\n" + " └─ Table\n" + " ├─ name: one_pk\n" + - " ├─ columns: [pk]\n" + - " ├─ colSet: (13-18)\n" + - " └─ tableId: 3\n" + + " └─ columns: [pk]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [a.pk, a.c1, a.c2, a.c3, a.c4, a.c5]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5)\n" + - " ├─ ((b.pk = c.pk) AND (b.pk = a.pk))\n" + + " └─ LeftOuterLookupJoin (estimated cost=16.500 rows=5)\n" + + " ├─ (b.pk = a.pk)\n" + " ├─ CrossJoin (estimated cost=17.160 rows=5)\n" + " │ ├─ TableAlias(c)\n" + " │ │ └─ Table\n" + @@ -16902,18 +16863,16 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk c1 c2 c3 c4 c5]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (c.pk, a.pk)\n" + - " ├─ right-key: (b.pk, b.pk)\n" + - " └─ TableAlias(b)\n" + - " └─ Table\n" + - " ├─ name: one_pk\n" + - " └─ columns: [pk]\n" + + " └─ TableAlias(b)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ columns: [pk]\n" + + " └─ keys: c.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [a.pk, a.c1, a.c2, a.c3, a.c4, a.c5]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5) (actual rows=16 loops=1)\n" + - " ├─ ((b.pk = c.pk) AND (b.pk = a.pk))\n" + + " └─ LeftOuterLookupJoin (estimated cost=16.500 rows=5) (actual rows=16 loops=1)\n" + + " ├─ (b.pk = a.pk)\n" + " ├─ CrossJoin (estimated cost=17.160 rows=5) (actual rows=16 loops=1)\n" + " │ ├─ TableAlias(c)\n" + " │ │ └─ Table\n" + @@ -16923,13 +16882,11 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk c1 c2 c3 c4 c5]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (c.pk, a.pk)\n" + - " ├─ right-key: (b.pk, b.pk)\n" + - " └─ TableAlias(b)\n" + - " └─ Table\n" + - " ├─ name: one_pk\n" + - " └─ columns: [pk]\n" + + " └─ TableAlias(b)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ columns: [pk]\n" + + " └─ keys: c.pk\n" + "", }, { @@ -17098,10 +17055,7 @@ inner join pq on true Query: `SELECT a.* FROM one_pk a CROSS JOIN one_pk b INNER JOIN one_pk c ON b.pk = c.pk LEFT JOIN one_pk d ON c.pk = d.pk`, ExpectedPlan: "Project\n" + " ├─ columns: [a.pk:2!null, a.c1:3, a.c2:4, a.c3:5, a.c4:6, a.c5:7]\n" + - " └─ LeftOuterHashJoin\n" + - " ├─ Eq\n" + - " │ ├─ c.pk:1!null\n" + - " │ └─ d.pk:8!null\n" + + " └─ LeftOuterLookupJoin\n" + " ├─ CrossJoin\n" + " │ ├─ MergeJoin\n" + " │ │ ├─ cmp: Eq\n" + @@ -17130,20 +17084,19 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk c1 c2 c3 c4 c5]\n" + - " └─ HashLookup\n" + - " ├─ left-key: TUPLE(c.pk:1!null)\n" + - " ├─ right-key: TUPLE(d.pk:0!null)\n" + - " └─ TableAlias(d)\n" + + " └─ TableAlias(d)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ keys: [c.pk:1!null]\n" + + " ├─ colSet: (19-24)\n" + + " ├─ tableId: 4\n" + " └─ Table\n" + " ├─ name: one_pk\n" + - " ├─ columns: [pk]\n" + - " ├─ colSet: (19-24)\n" + - " └─ tableId: 4\n" + + " └─ columns: [pk]\n" + "", ExpectedEstimates: "Project\n" + " ├─ columns: [a.pk, a.c1, a.c2, a.c3, a.c4, a.c5]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5)\n" + - " ├─ (c.pk = d.pk)\n" + + " └─ LeftOuterLookupJoin (estimated cost=16.500 rows=5)\n" + " ├─ CrossJoin (estimated cost=17.160 rows=5)\n" + " │ ├─ MergeJoin (estimated cost=8.120 rows=4)\n" + " │ │ ├─ cmp: (b.pk = c.pk)\n" + @@ -17161,18 +17114,15 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk c1 c2 c3 c4 c5]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (c.pk)\n" + - " ├─ right-key: (d.pk)\n" + - " └─ TableAlias(d)\n" + - " └─ Table\n" + - " ├─ name: one_pk\n" + - " └─ columns: [pk]\n" + + " └─ TableAlias(d)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ columns: [pk]\n" + + " └─ keys: c.pk\n" + "", ExpectedAnalysis: "Project\n" + " ├─ columns: [a.pk, a.c1, a.c2, a.c3, a.c4, a.c5]\n" + - " └─ LeftOuterHashJoin (estimated cost=17.100 rows=5) (actual rows=16 loops=1)\n" + - " ├─ (c.pk = d.pk)\n" + + " └─ LeftOuterLookupJoin (estimated cost=16.500 rows=5) (actual rows=16 loops=1)\n" + " ├─ CrossJoin (estimated cost=17.160 rows=5) (actual rows=16 loops=1)\n" + " │ ├─ MergeJoin (estimated cost=8.120 rows=4) (actual rows=4 loops=1)\n" + " │ │ ├─ cmp: (b.pk = c.pk)\n" + @@ -17190,13 +17140,11 @@ inner join pq on true " │ └─ Table\n" + " │ ├─ name: one_pk\n" + " │ └─ columns: [pk c1 c2 c3 c4 c5]\n" + - " └─ HashLookup\n" + - " ├─ left-key: (c.pk)\n" + - " ├─ right-key: (d.pk)\n" + - " └─ TableAlias(d)\n" + - " └─ Table\n" + - " ├─ name: one_pk\n" + - " └─ columns: [pk]\n" + + " └─ TableAlias(d)\n" + + " └─ IndexedTableAccess(one_pk)\n" + + " ├─ index: [one_pk.pk]\n" + + " ├─ columns: [pk]\n" + + " └─ keys: c.pk\n" + "", }, { @@ -18274,10 +18222,7 @@ inner join pq on true " ├─ Eq\n" + " │ ├─ a.pk:2!null\n" + " │ └─ l.v2:7\n" + - " ├─ LeftOuterHashJoin\n" + - " │ ├─ Eq\n" + - " │ │ ├─ a.pk:2!null\n" + - " │ │ └─ i.v1:0\n" + + " ├─ LeftOuterLookupJoin\n" + " │ ├─ MergeJoin\n" + " │ │ ├─ cmp: Eq\n" + " │ │ │ ├─ i.v1:0\n" + @@ -18300,14 +18245,15 @@ inner join pq on true " │ │ └─ Table\n" + " │ │ ├─ name: one_pk_three_idx\n" + " │ │ └─ columns: [pk]\n" + - " │ └─ HashLookup\n" + - " │ ├─ left-key: TUPLE(i.v1:0)\n" + - " │ ├─ right-key: TUPLE(a.pk:0!null)\n" + - " │ └─ TableAlias(a)\n" + - " │ └─ ProcessTable\n" + - " │ └─ Table\n" + - " │ ├─ name: one_pk_two_idx\n" + - " │ └─ columns: [pk v1 v2]\n" + + " │ └─ TableAlias(a)\n" + + " │ └─ IndexedTableAccess(one_pk_two_idx)\n" + + " │ ├─ index: [one_pk_two_idx.pk]\n" + + " │ ├─ keys: [i.v1:0]\n" + + " │ ├─ colSet: (1-3)\n" + + " │ ├─ tableId: 1\n" + + " │ └─ Table\n" + + " │ ├─ name: one_pk_two_idx\n" + + " │ └─ columns: [pk v1 v2]\n" + " └─ HashLookup\n" + " ├─ left-key: TUPLE(a.pk:2!null)\n" + " ├─ right-key: TUPLE(l.v2:2)\n" + @@ -18336,10 +18282,9 @@ inner join pq on true "", ExpectedEstimates: "Project\n" + " ├─ columns: [a.pk, a.v1, a.v2]\n" + - " └─ LeftOuterHashJoin (estimated cost=34.200 rows=10)\n" + + " └─ LeftOuterHashJoin (estimated cost=32.180 rows=10)\n" + " ├─ (a.pk = l.v2)\n" + - " ├─ LeftOuterHashJoin (estimated cost=32.180 rows=10)\n" + - " │ ├─ (a.pk = i.v1)\n" + + " ├─ LeftOuterLookupJoin (estimated cost=26.400 rows=8)\n" + " │ ├─ MergeJoin (estimated cost=16.240 rows=8)\n" + " │ │ ├─ cmp: (i.v1 = j.pk)\n" + " │ │ ├─ TableAlias(i)\n" + @@ -18352,13 +18297,11 @@ inner join pq on true " │ │ ├─ index: [one_pk_three_idx.pk]\n" + " │ │ ├─ filters: [{[NULL, ∞)}]\n" + " │ │ └─ columns: [pk]\n" + - " │ └─ HashLookup\n" + - " │ ├─ left-key: (i.v1)\n" + - " │ ├─ right-key: (a.pk)\n" + - " │ └─ TableAlias(a)\n" + - " │ └─ Table\n" + - " │ ├─ name: one_pk_two_idx\n" + - " │ └─ columns: [pk v1 v2]\n" + + " │ └─ TableAlias(a)\n" + + " │ └─ IndexedTableAccess(one_pk_two_idx)\n" + + " │ ├─ index: [one_pk_two_idx.pk]\n" + + " │ ├─ columns: [pk v1 v2]\n" + + " │ └─ keys: i.v1\n" + " └─ HashLookup\n" + " ├─ left-key: (a.pk)\n" + " ├─ right-key: (l.v2)\n" + @@ -18377,10 +18320,9 @@ inner join pq on true "", ExpectedAnalysis: "Project\n" + " ├─ columns: [a.pk, a.v1, a.v2]\n" + - " └─ LeftOuterHashJoin (estimated cost=34.200 rows=10) (actual rows=11 loops=1)\n" + + " └─ LeftOuterHashJoin (estimated cost=32.180 rows=10) (actual rows=11 loops=1)\n" + " ├─ (a.pk = l.v2)\n" + - " ├─ LeftOuterHashJoin (estimated cost=32.180 rows=10) (actual rows=8 loops=1)\n" + - " │ ├─ (a.pk = i.v1)\n" + + " ├─ LeftOuterLookupJoin (estimated cost=26.400 rows=8) (actual rows=8 loops=1)\n" + " │ ├─ MergeJoin (estimated cost=16.240 rows=8) (actual rows=8 loops=1)\n" + " │ │ ├─ cmp: (i.v1 = j.pk)\n" + " │ │ ├─ TableAlias(i)\n" + @@ -18393,13 +18335,11 @@ inner join pq on true " │ │ ├─ index: [one_pk_three_idx.pk]\n" + " │ │ ├─ filters: [{[NULL, ∞)}]\n" + " │ │ └─ columns: [pk]\n" + - " │ └─ HashLookup\n" + - " │ ├─ left-key: (i.v1)\n" + - " │ ├─ right-key: (a.pk)\n" + - " │ └─ TableAlias(a)\n" + - " │ └─ Table\n" + - " │ ├─ name: one_pk_two_idx\n" + - " │ └─ columns: [pk v1 v2]\n" + + " │ └─ TableAlias(a)\n" + + " │ └─ IndexedTableAccess(one_pk_two_idx)\n" + + " │ ├─ index: [one_pk_two_idx.pk]\n" + + " │ ├─ columns: [pk v1 v2]\n" + + " │ └─ keys: i.v1\n" + " └─ HashLookup\n" + " ├─ left-key: (a.pk)\n" + " ├─ right-key: (l.v2)\n" + @@ -25092,4 +25032,79 @@ order by x, y; " └─ columns: [s2 i2]\n" + "", }, + { + Query: `select * from id_parent child left join id_parent parent on parent.id = child.parent left join id_parent grandparent on grandparent.id = parent.parent where child.id in ('test_0', 'test_5000', 'test_10000')`, + ExpectedPlan: "Project\n" + + " ├─ columns: [child.id:0!null, child.parent:1, parent.id:2!null, parent.parent:3, grandparent.id:4!null, grandparent.parent:5]\n" + + " └─ LeftOuterLookupJoin\n" + + " ├─ LeftOuterLookupJoin\n" + + " │ ├─ TableAlias(child)\n" + + " │ │ └─ IndexedTableAccess(id_parent)\n" + + " │ │ ├─ index: [id_parent.id]\n" + + " │ │ ├─ static: [{[test_0, test_0]}, {[test_10000, test_10000]}, {[test_5000, test_5000]}]\n" + + " │ │ ├─ colSet: (1,2)\n" + + " │ │ ├─ tableId: 1\n" + + " │ │ └─ Table\n" + + " │ │ ├─ name: id_parent\n" + + " │ │ └─ columns: [id parent]\n" + + " │ └─ TableAlias(parent)\n" + + " │ └─ IndexedTableAccess(id_parent)\n" + + " │ ├─ index: [id_parent.id]\n" + + " │ ├─ keys: [child.parent:1]\n" + + " │ ├─ colSet: (3,4)\n" + + " │ ├─ tableId: 2\n" + + " │ └─ Table\n" + + " │ ├─ name: id_parent\n" + + " │ └─ columns: [id parent]\n" + + " └─ TableAlias(grandparent)\n" + + " └─ IndexedTableAccess(id_parent)\n" + + " ├─ index: [id_parent.id]\n" + + " ├─ keys: [parent.parent:3]\n" + + " ├─ colSet: (5,6)\n" + + " ├─ tableId: 3\n" + + " └─ Table\n" + + " ├─ name: id_parent\n" + + " └─ columns: [id parent]\n" + + "", + ExpectedEstimates: "Project\n" + + " ├─ columns: [child.id, child.parent, parent.id, parent.parent, grandparent.id, grandparent.parent]\n" + + " └─ LeftOuterLookupJoin (estimated cost=3.300 rows=1)\n" + + " ├─ LeftOuterLookupJoin (estimated cost=3.300 rows=1)\n" + + " │ ├─ TableAlias(child)\n" + + " │ │ └─ IndexedTableAccess(id_parent)\n" + + " │ │ ├─ index: [id_parent.id]\n" + + " │ │ ├─ filters: [{[test_0, test_0]}, {[test_10000, test_10000]}, {[test_5000, test_5000]}]\n" + + " │ │ └─ columns: [id parent]\n" + + " │ └─ TableAlias(parent)\n" + + " │ └─ IndexedTableAccess(id_parent)\n" + + " │ ├─ index: [id_parent.id]\n" + + " │ ├─ columns: [id parent]\n" + + " │ └─ keys: child.parent\n" + + " └─ TableAlias(grandparent)\n" + + " └─ IndexedTableAccess(id_parent)\n" + + " ├─ index: [id_parent.id]\n" + + " ├─ columns: [id parent]\n" + + " └─ keys: parent.parent\n" + + "", + ExpectedAnalysis: "Project\n" + + " ├─ columns: [child.id, child.parent, parent.id, parent.parent, grandparent.id, grandparent.parent]\n" + + " └─ LeftOuterLookupJoin (estimated cost=3.300 rows=1) (actual rows=0 loops=1)\n" + + " ├─ LeftOuterLookupJoin (estimated cost=3.300 rows=1) (actual rows=0 loops=1)\n" + + " │ ├─ TableAlias(child)\n" + + " │ │ └─ IndexedTableAccess(id_parent)\n" + + " │ │ ├─ index: [id_parent.id]\n" + + " │ │ ├─ filters: [{[test_0, test_0]}, {[test_10000, test_10000]}, {[test_5000, test_5000]}]\n" + + " │ │ └─ columns: [id parent]\n" + + " │ └─ TableAlias(parent)\n" + + " │ └─ IndexedTableAccess(id_parent)\n" + + " │ ├─ index: [id_parent.id]\n" + + " │ ├─ columns: [id parent]\n" + + " │ └─ keys: child.parent\n" + + " └─ TableAlias(grandparent)\n" + + " └─ IndexedTableAccess(id_parent)\n" + + " ├─ index: [id_parent.id]\n" + + " ├─ columns: [id parent]\n" + + " └─ keys: parent.parent\n" + + "", + }, } diff --git a/enginetest/queries/tpch_plans.go b/enginetest/queries/tpch_plans.go index 4e296b0336..ca0ed87087 100644 --- a/enginetest/queries/tpch_plans.go +++ b/enginetest/queries/tpch_plans.go @@ -2455,7 +2455,7 @@ order by " ├─ columns: [partsupp.PS_PARTKEY, partsupp.PS_SUPPKEY, partsupp.PS_AVAILQTY, partsupp.PS_SUPPLYCOST, partsupp.PS_COMMENT, part.P_PARTKEY, part.P_NAME, part.P_MFGR, part.P_BRAND, part.P_TYPE, part.P_SIZE, part.P_CONTAINER, part.P_RETAILPRICE, part.P_COMMENT]\n" + " └─ Filter\n" + " ├─ supplier.s_suppkey IS NULL\n" + - " └─ LeftOuterLookupJoin (estimated cost=3463.300 rows=1062)\n" + + " └─ LeftOuterLookupJoin (estimated cost=3300.000 rows=1000)\n" + " ├─ MergeJoin (estimated cost=1878.500 rows=1000)\n" + " │ ├─ cmp: (partsupp.ps_partkey = part.p_partkey)\n" + " │ ├─ IndexedTableAccess(partsupp)\n" + @@ -2486,7 +2486,7 @@ order by " ├─ columns: [partsupp.PS_PARTKEY, partsupp.PS_SUPPKEY, partsupp.PS_AVAILQTY, partsupp.PS_SUPPLYCOST, partsupp.PS_COMMENT, part.P_PARTKEY, part.P_NAME, part.P_MFGR, part.P_BRAND, part.P_TYPE, part.P_SIZE, part.P_CONTAINER, part.P_RETAILPRICE, part.P_COMMENT]\n" + " └─ Filter\n" + " ├─ supplier.s_suppkey IS NULL\n" + - " └─ LeftOuterLookupJoin (estimated cost=3463.300 rows=1062) (actual rows=0 loops=1)\n" + + " └─ LeftOuterLookupJoin (estimated cost=3300.000 rows=1000) (actual rows=0 loops=1)\n" + " ├─ MergeJoin (estimated cost=1878.500 rows=1000) (actual rows=0 loops=1)\n" + " │ ├─ cmp: (partsupp.ps_partkey = part.p_partkey)\n" + " │ ├─ IndexedTableAccess(partsupp)\n" + diff --git a/enginetest/scriptgen/setup/helper.go b/enginetest/scriptgen/setup/helper.go index 5c26a770da..678a7e5a03 100644 --- a/enginetest/scriptgen/setup/helper.go +++ b/enginetest/scriptgen/setup/helper.go @@ -116,5 +116,6 @@ var ( Invert_pkData, JoinData, Comp_index_tablesData, + Id_parentData, } ) diff --git a/enginetest/scriptgen/setup/scripts/id_parent b/enginetest/scriptgen/setup/scripts/id_parent new file mode 100644 index 0000000000..8f05ea46ca --- /dev/null +++ b/enginetest/scriptgen/setup/scripts/id_parent @@ -0,0 +1,7 @@ +exec +create table id_parent(id varchar(255), parent varchar(255), primary key (id)); +---- + +exec +analyze table id_parent update histogram on (id) using data '{"row_count": 28000}'; +---- \ No newline at end of file diff --git a/enginetest/scriptgen/setup/setup_data.sg.go b/enginetest/scriptgen/setup/setup_data.sg.go index 2173f99a5b..727bca3377 100755 --- a/enginetest/scriptgen/setup/setup_data.sg.go +++ b/enginetest/scriptgen/setup/setup_data.sg.go @@ -217,6 +217,11 @@ var Graph_tablesData = []SetupScript{{ ('filling', 'butter', 3)`, }} +var Id_parentData = []SetupScript{{ + `create table id_parent(id varchar(255), parent varchar(255), primary key (id));`, + `analyze table id_parent update histogram on (id) using data '{"row_count": 28000}';`, +}} + var ImdbData = []SetupScript{{ `CREATE TABLE aka_name ( id integer NOT NULL PRIMARY KEY, diff --git a/sql/func_deps.go b/sql/func_deps.go index 6cf59b7d54..8517c3ed60 100644 --- a/sql/func_deps.go +++ b/sql/func_deps.go @@ -123,6 +123,10 @@ type FuncDepSet struct { consts ColSet // tracks in-scope equivalent closure equivs *EquivSets + // conditionalEquivSets tracks equivalency sets from outer joins: + // These equivalence sets hold if + // at least one of the columns in the key is non-null + conditionalEquivSets map[ColSet]*EquivSets // keys includes the set of primary and secondary keys // accumulated in the relation. The first key is the best // key we have seen so far, where strict > lax and shorter @@ -213,22 +217,25 @@ func (f *FuncDepSet) String() string { b.WriteString(fmt.Sprintf("%s%s", sep, f.equivs)) sep = "; " } - if len(f.keys) < 2 { - return b.String() + for conditionalCols, equivSet := range f.conditionalEquivSets { + b.WriteString(fmt.Sprintf("%snonnull%s->%s", sep, conditionalCols, equivSet)) + sep = "; " } - for _, k := range f.keys[1:] { - var cols string - if k.allCols == f.all { - cols = k.cols.String() - } else { - cols = fmt.Sprintf("%s/%s", k.cols, k.allCols) - } - if k.strict { - b.WriteString(fmt.Sprintf("%sfd%s", sep, cols)) - } else { - b.WriteString(fmt.Sprintf("%slax-fd%s", sep, cols)) + if len(f.keys) >= 2 { + for _, k := range f.keys[1:] { + var cols string + if k.allCols == f.all { + cols = k.cols.String() + } else { + cols = fmt.Sprintf("%s/%s", k.cols, k.allCols) + } + if k.strict { + b.WriteString(fmt.Sprintf("%sfd%s", sep, cols)) + } else { + b.WriteString(fmt.Sprintf("%slax-fd%s", sep, cols)) + } + sep = "; " } - sep = "; " } return b.String() } @@ -237,12 +244,24 @@ func (f *FuncDepSet) Constants() ColSet { return f.consts } +// EquivalenceClosure computes the set of columns that are known to be equal to at least one of +// the columns in the input column set, considering conditional equivalence sets and the current +// nullability information. func (f *FuncDepSet) EquivalenceClosure(cols ColSet) ColSet { for _, set := range f.equivs.Sets() { if set.Intersects(cols) { cols = cols.Union(set) } } + for conditionalCols, equivSet := range f.conditionalEquivSets { + if conditionalCols.Intersects(f.notNull) { + for _, set := range equivSet.Sets() { + if set.Intersects(cols) { + cols = cols.Union(set) + } + } + } + } return cols } @@ -255,6 +274,7 @@ func (f *FuncDepSet) AddConstants(cols ColSet) { f.consts = f.consts.Union(cols) } +// AddEquiv records that columns i and j are always equal func (f *FuncDepSet) AddEquiv(i, j ColumnId) { cols := NewColSet(i, j) if f.equivs == nil { @@ -263,6 +283,7 @@ func (f *FuncDepSet) AddEquiv(i, j ColumnId) { f.AddEquivSet(cols) } +// AddEquivSet records that the provided columns are always equal. func (f *FuncDepSet) AddEquivSet(cols ColSet) { if f.equivs == nil { f.equivs = &EquivSets{} @@ -276,6 +297,45 @@ func (f *FuncDepSet) AddEquivSet(cols ColSet) { } } +// AddEquivSet imports all equivSets and conditionalEquivSets from another FDS. +func (f *FuncDepSet) AddEquivSets(other *FuncDepSet) { + if f.equivs == nil { + f.equivs = &EquivSets{} + } + for _, equivSet := range other.equivs.Sets() { + f.AddEquivSet(equivSet) + } + for conditionalCols, equivSet := range other.conditionalEquivSets { + // If one of the columns is known to be non-null, we can promote conditional equivalence sets + // to ordinary equivalence sets. This is most likely to happen when constructing a lookup FDS + // from an outer join: the join column is implicitly nullable because of the outer join, but + // the column on underlying table is non-null. + if conditionalCols.Intersects(f.notNull) { + for _, set := range equivSet.Sets() { + f.AddEquivSet(set) + } + } else { + for _, colSet := range equivSet.Sets() { + f.AddConditionalEquiv(conditionalCols, colSet) + } + } + } +} + +// AddConditionalEquiv records the columns in |equivCols| are equal if at least one of the columns +// in |conditionCols| is non-null. This is usually caused by outer joins. +func (f *FuncDepSet) AddConditionalEquiv(conditionCols, equivCols ColSet) { + if f.conditionalEquivSets == nil { + f.conditionalEquivSets = make(map[ColSet]*EquivSets) + } + outerEquivSet, ok := f.conditionalEquivSets[conditionCols] + if !ok { + outerEquivSet = &EquivSets{} + f.conditionalEquivSets[conditionCols] = outerEquivSet + } + outerEquivSet.Add(equivCols) +} + func (f *FuncDepSet) AddKey(k Key) { switch k.strict { case true: @@ -339,8 +399,7 @@ func (f *FuncDepSet) AddLaxKey(cols ColSet) { } } -// simplifyCols uses equivalence and constant sets to minimize -// a key set +// simplifyCols uses equivalence and constant sets to minimize a key set func (f *FuncDepSet) simplifyCols(key ColSet, subKeys []Key) ColSet { if key.Empty() { return key @@ -423,12 +482,8 @@ func NewCrossJoinFDs(left, right *FuncDepSet) *FuncDepSet { ret.AddNotNullable(right.notNull) ret.AddConstants(left.consts) ret.AddConstants(right.consts) - for _, set := range left.equivs.Sets() { - ret.AddEquivSet(set) - } - for _, set := range right.equivs.Sets() { - ret.AddEquivSet(set) - } + ret.AddEquivSets(left) + ret.AddEquivSets(right) // concatenate lead key, append others var lKey, rKey Key if len(left.keys) > 0 { @@ -471,12 +526,8 @@ func NewInnerJoinFDs(left, right *FuncDepSet, filters [][2]ColumnId) *FuncDepSet } else { ret.AddConstants(right.consts) } - for _, set := range left.Equiv().Sets() { - ret.AddEquivSet(set) - } - for _, set := range right.Equiv().Sets() { - ret.AddEquivSet(set) - } + ret.AddEquivSets(left) + ret.AddEquivSets(right) for _, f := range filters { ret.AddEquiv(f[0], f[1]) } @@ -521,9 +572,7 @@ func NewFilterFDs(fds *FuncDepSet, notNull ColSet, constant ColSet, equiv [][2]C ret := &FuncDepSet{all: fds.All()} ret.AddNotNullable(fds.notNull.Union(notNull)) ret.AddConstants(fds.Constants().Union(constant)) - for _, e := range fds.equivs.Sets() { - ret.AddEquivSet(e) - } + ret.AddEquivSets(fds) for _, e := range equiv { ret.AddEquiv(e[0], e[1]) } @@ -533,15 +582,13 @@ func NewFilterFDs(fds *FuncDepSet, notNull ColSet, constant ColSet, equiv [][2]C return ret } -func NewLookupFDs(fds *FuncDepSet, idxCols ColSet, notNull ColSet, constants ColSet, equiv *EquivSets) *FuncDepSet { +func NewLookupFDs(fds *FuncDepSet, idxCols ColSet, notNull ColSet, constants ColSet, joinFds *FuncDepSet) *FuncDepSet { ret := &FuncDepSet{all: fds.All()} ret.AddNotNullable(fds.notNull.Union(notNull)) ret.AddConstants(fds.Constants().Union(constants)) - for _, e := range fds.equivs.Sets() { - ret.AddEquivSet(e) - } - for _, set := range equiv.Sets() { - ret.AddEquivSet(set) + ret.AddEquivSets(fds) + if joinFds != nil { + ret.AddEquivSets(joinFds) } ret.AddLaxKey(idxCols) return ret @@ -661,9 +708,12 @@ func NewLeftJoinFDs(left, right *FuncDepSet, filters [][2]ColumnId) *FuncDepSet } ret.AddConstants(leftConst) } - // only left equiv holds - for _, equiv := range left.equivs.Sets() { - ret.AddEquivSet(equiv) + // Because this is a left join, there may be rows where every column from the right child is NULL, + // even if those columns are non-null in the right child schema. Equivalence sets from the right child + // only hold if at least one of the columns from the right child is non-null. + ret.AddEquivSets(left) + for _, f := range filters { + ret.AddConditionalEquiv(right.all, NewColSet(f[0], f[1])) } if leftStrict && leftColsAreInnerJoinKey { diff --git a/sql/func_deps_test.go b/sql/func_deps_test.go index 0e2a05335b..fc912ef7bd 100644 --- a/sql/func_deps_test.go +++ b/sql/func_deps_test.go @@ -334,6 +334,7 @@ func TestFuncDeps_LeftJoin(t *testing.T) { assert.Equal(t, "key(1,6,7); lax-fd(2)/(1-5)", join.String()) }) t.Run("equiv on both sides left join", func(t *testing.T) { + // SELECT * FROM abcde RIGHT JOIN mnpq abcde := &FuncDepSet{all: cols(1, 2, 3, 4, 5)} abcde.AddNotNullable(cols(1)) abcde.AddEquivSet(cols(2, 3, 4)) @@ -360,7 +361,20 @@ func TestFuncDeps_LeftJoin(t *testing.T) { mnpq.AddStrictKey(cols(6, 7)) join := NewLeftJoinFDs(mnpq, abcde, [][2]ColumnId{{1, 6}}) - assert.Equal(t, "key(6,7); fd(1)/(1-5); lax-fd(2,3)/(1-5)", join.String()) + assert.Equal(t, "key(6,7); nonnull(1-5)->equiv(1,6); fd(1)/(1-5); lax-fd(2,3)/(1-5)", join.String()) + }) + t.Run("join filter no partial equiv", func(t *testing.T) { + // SELECT * FROM abcde RIGHT OUTER JOIN mnpq ON a=m + abcde := &FuncDepSet{all: cols(1, 2, 3, 4, 5)} + abcde.AddStrictKey(cols(1)) + abcde.AddLaxKey(cols(2, 3)) + + mnpq := &FuncDepSet{all: cols(6, 7, 8, 9)} + mnpq.AddNotNullable(cols(6, 7)) + mnpq.AddStrictKey(cols(6, 7)) + + join := NewLeftJoinFDs(mnpq, abcde, [][2]ColumnId{{1, 6}}) + assert.Equal(t, "key(6,7); nonnull(1-5)->equiv(1,6); fd(1)/(1-5); lax-fd(2,3)/(1-5)", join.String()) }) t.Run("join filter equiv and null-side rel equiv", func(t *testing.T) { // SELECT * FROM abcde RIGHT OUTER JOIN mnpq ON a=m AND a=b @@ -374,7 +388,7 @@ func TestFuncDeps_LeftJoin(t *testing.T) { mnpq.AddStrictKey(cols(6, 7)) join := NewLeftJoinFDs(mnpq, abcde, [][2]ColumnId{{1, 6}, {1, 2}}) - assert.Equal(t, "key(6,7); fd(1)/(1-5); lax-fd(2,3)/(1-5)", join.String()) + assert.Equal(t, "key(6,7); nonnull(1-5)->equiv(1,2,6); fd(1)/(1-5); lax-fd(2,3)/(1-5)", join.String()) }) t.Run("max1Row left join", func(t *testing.T) { abcde := &FuncDepSet{all: cols(1, 2, 3, 4, 5)} @@ -390,7 +404,7 @@ func TestFuncDeps_LeftJoin(t *testing.T) { mnpq.AddStrictKey(cols(6, 7)) join := NewLeftJoinFDs(mnpq, abcde, [][2]ColumnId{{1, 6}, {1, 2}}) - assert.Equal(t, "key(); constant(1,6,7)", join.String()) + assert.Equal(t, "key(); constant(1,6,7); nonnull(1-5)->equiv(1,2,6)", join.String()) }) } diff --git a/sql/memo/coster.go b/sql/memo/coster.go index a313f6aa00..fc3bb9d497 100644 --- a/sql/memo/coster.go +++ b/sql/memo/coster.go @@ -269,7 +269,7 @@ func isInjectiveLookup(idx *Index, joinBase *JoinBase, keyExprs []sql.Expression constCols = constCols.Union(onCols) } - fds := sql.NewLookupFDs(joinBase.Right.RelProps.FuncDeps(), idx.ColSet(), notNull, constCols, joinFds.Equiv()) + fds := sql.NewLookupFDs(joinBase.Right.RelProps.FuncDeps(), idx.ColSet(), notNull, constCols, joinFds) return fds.HasMax1Row() }