diff --git a/pkg/sql/opt/exec/execbuilder/testdata/explain b/pkg/sql/opt/exec/execbuilder/testdata/explain index 1983c8ea22b3..30337f502f74 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/explain +++ b/pkg/sql/opt/exec/execbuilder/testdata/explain @@ -1411,26 +1411,20 @@ vectorized: true │ auto commit │ arbiter indexes: primary, u_v_key │ -└── • filter - │ filter: k IS NULL +└── • lookup join (anti) + │ table: u@u_v_key + │ equality: (column2) = (v) + │ equality cols are key │ - └── • lookup join (left outer) - │ table: u@u_v_key - │ equality: (column2) = (v) - │ equality cols are key + └── • cross join (anti) │ - └── • filter - │ filter: k IS NULL - │ - └── • cross join (left outer) - │ - ├── • values - │ size: 2 columns, 1 row - │ - └── • scan - missing stats - table: u@primary - spans: [/1 - /1] + ├── • values + │ size: 2 columns, 1 row + │ + └── • scan + missing stats + table: u@primary + spans: [/1 - /1] # Make sure EXPLAIN (VERBOSE) works when there is a constrained scan of a # virtual table (#58193). diff --git a/pkg/sql/opt/memo/testdata/logprops/upsert b/pkg/sql/opt/memo/testdata/logprops/upsert index 8745fed91bbf..0bf5bddad6dc 100644 --- a/pkg/sql/opt/memo/testdata/logprops/upsert +++ b/pkg/sql/opt/memo/testdata/logprops/upsert @@ -236,198 +236,136 @@ project ├── volatile ├── key: (6) ├── fd: (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11), (7,11)~~>(6,10) - ├── project + ├── anti-join (hash) │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) │ ├── volatile │ ├── key: (6) │ ├── fd: (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11) - │ ├── prune: (6,7,10,11) - │ └── select - │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:22(int) b:23(int) c:24(int) rowid:25(int) - │ ├── volatile - │ ├── key: (6) - │ ├── fd: ()-->(22-25), (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11) - │ ├── prune: (22) - │ ├── interesting orderings: (+25) (+22) (+23,+24,+25) - │ ├── left-join (hash) - │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:22(int) b:23(int) c:24(int) rowid:25(int) - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ ├── volatile - │ │ ├── key: (6,25) - │ │ ├── fd: (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11), (25)-->(22-24), (22)-->(23-25), (23,24)~~>(22,25) - │ │ ├── prune: (22,25) - │ │ ├── reject-nulls: (22-25) - │ │ ├── interesting orderings: (+25) (+22) (+23,+24,+25) - │ │ ├── upsert-distinct-on - │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) - │ │ │ ├── grouping columns: x:6(int!null) - │ │ │ ├── volatile - │ │ │ ├── key: (6) - │ │ │ ├── fd: (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11) - │ │ │ ├── project - │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) - │ │ │ │ ├── volatile - │ │ │ │ ├── key: (6) - │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11), (10)~~>(6,7,11) - │ │ │ │ ├── prune: (6,7,10,11) - │ │ │ │ └── select - │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:17(int) b:18(int) c:19(int) rowid:20(int) - │ │ │ │ ├── volatile - │ │ │ │ ├── key: (6) - │ │ │ │ ├── fd: ()-->(17-20), (6)-->(7,10), (7)-->(11), (10)~~>(6,7,11) - │ │ │ │ ├── prune: (18-20) - │ │ │ │ ├── interesting orderings: (+20) (+17) (+18,+19,+20) - │ │ │ │ ├── left-join (hash) - │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:17(int) b:18(int) c:19(int) rowid:20(int) - │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one) - │ │ │ │ │ ├── volatile - │ │ │ │ │ ├── key: (6) - │ │ │ │ │ ├── fd: (6)-->(7,10,17-20), (7)-->(11), (10)~~>(6,7,11), (20)-->(17-19), (17)-->(18-20), (18,19)~~>(17,20) - │ │ │ │ │ ├── prune: (18-20) - │ │ │ │ │ ├── reject-nulls: (17-20) - │ │ │ │ │ ├── interesting orderings: (+20) (+17) (+18,+19,+20) - │ │ │ │ │ ├── upsert-distinct-on - │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) - │ │ │ │ │ │ ├── grouping columns: column10:10(int) - │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11), (10)~~>(6,7,11) - │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) - │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11) - │ │ │ │ │ │ │ ├── prune: (6,7,10,11) - │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) - │ │ │ │ │ │ │ └── select - │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:12(int) b:13(int) c:14(int) rowid:15(int) - │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ ├── fd: ()-->(12-15), (6)-->(7,10), (7)-->(11) - │ │ │ │ │ │ │ ├── prune: (6,7,11-14) - │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) (+15) (+12) (+13,+14,+15) - │ │ │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) a:12(int) b:13(int) c:14(int) rowid:15(int) - │ │ │ │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ │ ├── fd: (6)-->(7,10,12-15), (7)-->(11), (15)-->(12-14), (12)-->(13-15), (13,14)~~>(12,15) - │ │ │ │ │ │ │ │ ├── prune: (6,7,11-14) - │ │ │ │ │ │ │ │ ├── reject-nulls: (12-15) - │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) (+15) (+12) (+13,+14,+15) - │ │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ │ ├── columns: column11:11(int) x:6(int!null) y:7(int) column10:10(int) - │ │ │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11) - │ │ │ │ │ │ │ │ │ ├── prune: (6,7,10,11) - │ │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) - │ │ │ │ │ │ │ │ │ ├── unfiltered-cols: (6-9) - │ │ │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ │ │ ├── columns: column10:10(int) x:6(int!null) y:7(int) - │ │ │ │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ │ │ │ ├── fd: (6)-->(7,10) - │ │ │ │ │ │ │ │ │ │ ├── prune: (6,7,10) - │ │ │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) - │ │ │ │ │ │ │ │ │ │ ├── unfiltered-cols: (6-9) - │ │ │ │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) - │ │ │ │ │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ │ │ │ │ ├── fd: (6)-->(7) - │ │ │ │ │ │ │ │ │ │ │ ├── prune: (6,7) - │ │ │ │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) - │ │ │ │ │ │ │ │ │ │ │ ├── unfiltered-cols: (6-9) - │ │ │ │ │ │ │ │ │ │ │ └── scan xyz - │ │ │ │ │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) z:8(int) xyz.crdb_internal_mvcc_timestamp:9(decimal) - │ │ │ │ │ │ │ │ │ │ │ ├── key: (6) - │ │ │ │ │ │ │ │ │ │ │ ├── fd: (6)-->(7-9), (7,8)~~>(6,9) - │ │ │ │ │ │ │ │ │ │ │ ├── prune: (6-9) - │ │ │ │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7,+8,+6) (+8,+7,+6) - │ │ │ │ │ │ │ │ │ │ │ └── unfiltered-cols: (6-9) - │ │ │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ │ │ └── function: unique_rowid [as=column10:10, type=int, volatile] - │ │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ │ └── plus [as=column11:11, type=int, outer=(7), immutable] - │ │ │ │ │ │ │ │ │ ├── variable: y:7 [type=int] - │ │ │ │ │ │ │ │ │ └── const: 1 [type=int] - │ │ │ │ │ │ │ │ ├── scan abc - │ │ │ │ │ │ │ │ │ ├── columns: a:12(int!null) b:13(int) c:14(int) rowid:15(int!null) - │ │ │ │ │ │ │ │ │ ├── computed column expressions - │ │ │ │ │ │ │ │ │ │ └── c:14 - │ │ │ │ │ │ │ │ │ │ └── plus [type=int] - │ │ │ │ │ │ │ │ │ │ ├── variable: b:13 [type=int] - │ │ │ │ │ │ │ │ │ │ └── const: 1 [type=int] - │ │ │ │ │ │ │ │ │ ├── key: (15) - │ │ │ │ │ │ │ │ │ ├── fd: (15)-->(12-14), (12)-->(13-15), (13,14)~~>(12,15) - │ │ │ │ │ │ │ │ │ ├── prune: (12-15) - │ │ │ │ │ │ │ │ │ ├── interesting orderings: (+15) (+12) (+13,+14,+15) - │ │ │ │ │ │ │ │ │ └── unfiltered-cols: (12-16) - │ │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ │ └── eq [type=bool, outer=(10,15), constraints=(/10: (/NULL - ]; /15: (/NULL - ]), fd=(10)==(15), (15)==(10)] - │ │ │ │ │ │ │ │ ├── variable: column10:10 [type=int] - │ │ │ │ │ │ │ │ └── variable: rowid:15 [type=int] - │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── is [type=bool, outer=(15), constraints=(/15: [/NULL - /NULL]; tight), fd=()-->(15)] - │ │ │ │ │ │ │ ├── variable: rowid:15 [type=int] - │ │ │ │ │ │ │ └── null [type=unknown] - │ │ │ │ │ │ └── aggregations - │ │ │ │ │ │ ├── first-agg [as=x:6, type=int, outer=(6)] - │ │ │ │ │ │ │ └── variable: x:6 [type=int] - │ │ │ │ │ │ ├── first-agg [as=y:7, type=int, outer=(7)] - │ │ │ │ │ │ │ └── variable: y:7 [type=int] - │ │ │ │ │ │ └── first-agg [as=column11:11, type=int, outer=(11)] - │ │ │ │ │ │ └── variable: column11:11 [type=int] - │ │ │ │ │ ├── scan abc - │ │ │ │ │ │ ├── columns: a:17(int!null) b:18(int) c:19(int) rowid:20(int!null) - │ │ │ │ │ │ ├── computed column expressions - │ │ │ │ │ │ │ └── c:19 - │ │ │ │ │ │ │ └── plus [type=int] - │ │ │ │ │ │ │ ├── variable: b:18 [type=int] - │ │ │ │ │ │ │ └── const: 1 [type=int] - │ │ │ │ │ │ ├── key: (20) - │ │ │ │ │ │ ├── fd: (20)-->(17-19), (17)-->(18-20), (18,19)~~>(17,20) - │ │ │ │ │ │ ├── prune: (17-20) - │ │ │ │ │ │ ├── interesting orderings: (+20) (+17) (+18,+19,+20) - │ │ │ │ │ │ └── unfiltered-cols: (17-21) - │ │ │ │ │ └── filters - │ │ │ │ │ └── eq [type=bool, outer=(6,17), constraints=(/6: (/NULL - ]; /17: (/NULL - ]), fd=(6)==(17), (17)==(6)] - │ │ │ │ │ ├── variable: x:6 [type=int] - │ │ │ │ │ └── variable: a:17 [type=int] - │ │ │ │ └── filters - │ │ │ │ └── is [type=bool, outer=(17), constraints=(/17: [/NULL - /NULL]; tight), fd=()-->(17)] - │ │ │ │ ├── variable: a:17 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ └── aggregations - │ │ │ ├── first-agg [as=y:7, type=int, outer=(7)] - │ │ │ │ └── variable: y:7 [type=int] - │ │ │ ├── first-agg [as=column10:10, type=int, outer=(10)] - │ │ │ │ └── variable: column10:10 [type=int] - │ │ │ └── first-agg [as=column11:11, type=int, outer=(11)] - │ │ │ └── variable: column11:11 [type=int] - │ │ ├── scan abc - │ │ │ ├── columns: a:22(int!null) b:23(int) c:24(int) rowid:25(int!null) - │ │ │ ├── computed column expressions - │ │ │ │ └── c:24 - │ │ │ │ └── plus [type=int] - │ │ │ │ ├── variable: b:23 [type=int] - │ │ │ │ └── const: 1 [type=int] - │ │ │ ├── key: (25) - │ │ │ ├── fd: (25)-->(22-24), (22)-->(23-25), (23,24)~~>(22,25) - │ │ │ ├── prune: (22-25) - │ │ │ ├── interesting orderings: (+25) (+22) (+23,+24,+25) - │ │ │ └── unfiltered-cols: (22-26) - │ │ └── filters - │ │ ├── eq [type=bool, outer=(7,23), constraints=(/7: (/NULL - ]; /23: (/NULL - ]), fd=(7)==(23), (23)==(7)] - │ │ │ ├── variable: y:7 [type=int] - │ │ │ └── variable: b:23 [type=int] - │ │ └── eq [type=bool, outer=(11,24), constraints=(/11: (/NULL - ]; /24: (/NULL - ]), fd=(11)==(24), (24)==(11)] - │ │ ├── variable: column11:11 [type=int] - │ │ └── variable: c:24 [type=int] - │ └── filters - │ └── is [type=bool, outer=(25), constraints=(/25: [/NULL - /NULL]; tight), fd=()-->(25)] - │ ├── variable: rowid:25 [type=int] - │ └── null [type=unknown] + │ ├── upsert-distinct-on + │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) + │ │ ├── grouping columns: x:6(int!null) + │ │ ├── volatile + │ │ ├── key: (6) + │ │ ├── fd: (6)-->(7,10,11), (7)-->(11), (10)~~>(6,7,11) + │ │ ├── anti-join (hash) + │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) + │ │ │ ├── volatile + │ │ │ ├── key: (6) + │ │ │ ├── fd: (6)-->(7,10), (7)-->(11), (10)~~>(6,7,11) + │ │ │ ├── upsert-distinct-on + │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) + │ │ │ │ ├── grouping columns: column10:10(int) + │ │ │ │ ├── volatile + │ │ │ │ ├── key: (6) + │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11), (10)~~>(6,7,11) + │ │ │ │ ├── anti-join (hash) + │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) column10:10(int) column11:11(int) + │ │ │ │ │ ├── volatile + │ │ │ │ │ ├── key: (6) + │ │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11) + │ │ │ │ │ ├── prune: (6,7,11) + │ │ │ │ │ ├── interesting orderings: (+6) (+7) + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: column11:11(int) x:6(int!null) y:7(int) column10:10(int) + │ │ │ │ │ │ ├── volatile + │ │ │ │ │ │ ├── key: (6) + │ │ │ │ │ │ ├── fd: (6)-->(7,10), (7)-->(11) + │ │ │ │ │ │ ├── prune: (6,7,10,11) + │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) + │ │ │ │ │ │ ├── unfiltered-cols: (6-9) + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: column10:10(int) x:6(int!null) y:7(int) + │ │ │ │ │ │ │ ├── volatile + │ │ │ │ │ │ │ ├── key: (6) + │ │ │ │ │ │ │ ├── fd: (6)-->(7,10) + │ │ │ │ │ │ │ ├── prune: (6,7,10) + │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) + │ │ │ │ │ │ │ ├── unfiltered-cols: (6-9) + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) + │ │ │ │ │ │ │ │ ├── key: (6) + │ │ │ │ │ │ │ │ ├── fd: (6)-->(7) + │ │ │ │ │ │ │ │ ├── prune: (6,7) + │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7) + │ │ │ │ │ │ │ │ ├── unfiltered-cols: (6-9) + │ │ │ │ │ │ │ │ └── scan xyz + │ │ │ │ │ │ │ │ ├── columns: x:6(int!null) y:7(int) z:8(int) xyz.crdb_internal_mvcc_timestamp:9(decimal) + │ │ │ │ │ │ │ │ ├── key: (6) + │ │ │ │ │ │ │ │ ├── fd: (6)-->(7-9), (7,8)~~>(6,9) + │ │ │ │ │ │ │ │ ├── prune: (6-9) + │ │ │ │ │ │ │ │ ├── interesting orderings: (+6) (+7,+8,+6) (+8,+7,+6) + │ │ │ │ │ │ │ │ └── unfiltered-cols: (6-9) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── function: unique_rowid [as=column10:10, type=int, volatile] + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── plus [as=column11:11, type=int, outer=(7), immutable] + │ │ │ │ │ │ ├── variable: y:7 [type=int] + │ │ │ │ │ │ └── const: 1 [type=int] + │ │ │ │ │ ├── scan abc + │ │ │ │ │ │ ├── columns: a:12(int!null) b:13(int) c:14(int) rowid:15(int!null) + │ │ │ │ │ │ ├── computed column expressions + │ │ │ │ │ │ │ └── c:14 + │ │ │ │ │ │ │ └── plus [type=int] + │ │ │ │ │ │ │ ├── variable: b:13 [type=int] + │ │ │ │ │ │ │ └── const: 1 [type=int] + │ │ │ │ │ │ ├── key: (15) + │ │ │ │ │ │ ├── fd: (15)-->(12-14), (12)-->(13-15), (13,14)~~>(12,15) + │ │ │ │ │ │ ├── prune: (12-15) + │ │ │ │ │ │ ├── interesting orderings: (+15) (+12) (+13,+14,+15) + │ │ │ │ │ │ └── unfiltered-cols: (12-16) + │ │ │ │ │ └── filters + │ │ │ │ │ └── eq [type=bool, outer=(10,15), constraints=(/10: (/NULL - ]; /15: (/NULL - ]), fd=(10)==(15), (15)==(10)] + │ │ │ │ │ ├── variable: column10:10 [type=int] + │ │ │ │ │ └── variable: rowid:15 [type=int] + │ │ │ │ └── aggregations + │ │ │ │ ├── first-agg [as=x:6, type=int, outer=(6)] + │ │ │ │ │ └── variable: x:6 [type=int] + │ │ │ │ ├── first-agg [as=y:7, type=int, outer=(7)] + │ │ │ │ │ └── variable: y:7 [type=int] + │ │ │ │ └── first-agg [as=column11:11, type=int, outer=(11)] + │ │ │ │ └── variable: column11:11 [type=int] + │ │ │ ├── scan abc + │ │ │ │ ├── columns: a:17(int!null) b:18(int) c:19(int) rowid:20(int!null) + │ │ │ │ ├── computed column expressions + │ │ │ │ │ └── c:19 + │ │ │ │ │ └── plus [type=int] + │ │ │ │ │ ├── variable: b:18 [type=int] + │ │ │ │ │ └── const: 1 [type=int] + │ │ │ │ ├── key: (20) + │ │ │ │ ├── fd: (20)-->(17-19), (17)-->(18-20), (18,19)~~>(17,20) + │ │ │ │ ├── prune: (17-20) + │ │ │ │ ├── interesting orderings: (+20) (+17) (+18,+19,+20) + │ │ │ │ └── unfiltered-cols: (17-21) + │ │ │ └── filters + │ │ │ └── eq [type=bool, outer=(6,17), constraints=(/6: (/NULL - ]; /17: (/NULL - ]), fd=(6)==(17), (17)==(6)] + │ │ │ ├── variable: x:6 [type=int] + │ │ │ └── variable: a:17 [type=int] + │ │ └── aggregations + │ │ ├── first-agg [as=y:7, type=int, outer=(7)] + │ │ │ └── variable: y:7 [type=int] + │ │ ├── first-agg [as=column10:10, type=int, outer=(10)] + │ │ │ └── variable: column10:10 [type=int] + │ │ └── first-agg [as=column11:11, type=int, outer=(11)] + │ │ └── variable: column11:11 [type=int] + │ ├── scan abc + │ │ ├── columns: a:22(int!null) b:23(int) c:24(int) rowid:25(int!null) + │ │ ├── computed column expressions + │ │ │ └── c:24 + │ │ │ └── plus [type=int] + │ │ │ ├── variable: b:23 [type=int] + │ │ │ └── const: 1 [type=int] + │ │ ├── key: (25) + │ │ ├── fd: (25)-->(22-24), (22)-->(23-25), (23,24)~~>(22,25) + │ │ ├── prune: (22-25) + │ │ ├── interesting orderings: (+25) (+22) (+23,+24,+25) + │ │ └── unfiltered-cols: (22-26) + │ └── filters + │ ├── eq [type=bool, outer=(7,23), constraints=(/7: (/NULL - ]; /23: (/NULL - ]), fd=(7)==(23), (23)==(7)] + │ │ ├── variable: y:7 [type=int] + │ │ └── variable: b:23 [type=int] + │ └── eq [type=bool, outer=(11,24), constraints=(/11: (/NULL - ]; /24: (/NULL - ]), fd=(11)==(24), (24)==(11)] + │ ├── variable: column11:11 [type=int] + │ └── variable: c:24 [type=int] └── aggregations ├── first-agg [as=x:6, type=int, outer=(6)] │ └── variable: x:6 [type=int] diff --git a/pkg/sql/opt/norm/groupby_funcs.go b/pkg/sql/opt/norm/groupby_funcs.go index 2fa785ddcf52..c9b3e317ff5a 100644 --- a/pkg/sql/opt/norm/groupby_funcs.go +++ b/pkg/sql/opt/norm/groupby_funcs.go @@ -141,8 +141,8 @@ func (c *CustomFuncs) ConstructProjectionFromDistinctOn( // AreValuesDistinct returns true if a constant Values operator input contains // only rows that are already distinct with respect to the given grouping -// columns. The Values operator can be wrapped by Select, Project, and/or -// LeftJoin operators. +// columns. The Values operator can be wrapped by Select, Project, LeftJoin +// and/or AntiJoin operators. // // If nullsAreDistinct is true, then NULL values are treated as not equal to one // another, and therefore rows containing a NULL value in any grouping column @@ -190,6 +190,13 @@ func (c *CustomFuncs) AreValuesDistinct( return c.AreValuesDistinct(t.Left, groupingCols, nullsAreDistinct) + case *memo.AntiJoinExpr: + // Pass through call to left input if grouping on its columns. + leftCols := t.Left.Relational().OutputCols + if groupingCols.SubsetOf(leftCols) { + return c.AreValuesDistinct(t.Left, groupingCols, nullsAreDistinct) + } + case *memo.UpsertDistinctOnExpr: // Pass through call to input if grouping on passthrough columns. if groupingCols.SubsetOf(t.Input.Relational().OutputCols) { diff --git a/pkg/sql/opt/norm/rules/groupby.opt b/pkg/sql/opt/norm/rules/groupby.opt index 54155aa3d8c0..a62017c52642 100644 --- a/pkg/sql/opt/norm/rules/groupby.opt +++ b/pkg/sql/opt/norm/rules/groupby.opt @@ -11,13 +11,15 @@ # EliminateGroupByProject discards a nested Project operator that is only # removing columns from its input (and not synthesizing new ones). That's -# something the GroupBy operators can do on their own. +# something the GroupBy operators can do on their own. This rule does not match +# UpsertDistinctOn expressions because they are not built with a Project as a +# child, so there is no Project to eliminate. # # Note: EliminateGroupByProject should be located above # EliminateJoinUnderGroupByLeft so that it can remove any interfering Projects. [EliminateGroupByProject, Normalize] (GroupBy | ScalarGroupBy | DistinctOn | EnsureDistinctOn - | UpsertDistinctOn | EnsureUpsertDistinctOn + | EnsureUpsertDistinctOn $input:(Project $innerInput:*) & (ColsAreSubset (OutputCols $input) diff --git a/pkg/sql/opt/norm/testdata/rules/groupby b/pkg/sql/opt/norm/testdata/rules/groupby index 20ac65d01cab..19222ee21a31 100644 --- a/pkg/sql/opt/norm/testdata/rules/groupby +++ b/pkg/sql/opt/norm/testdata/rules/groupby @@ -718,66 +718,6 @@ project └── projections └── xy.y:8 [as=y:10, outer=(8)] -# UpsertDistinctOn case. -norm expect=EliminateGroupByProject -INSERT INTO nullablecols (rowid, c1, c2, c3) -SELECT i, i, i, i FROM (SELECT * FROM a WHERE EXISTS(SELECT * FROM a) AND k>0) -ON CONFLICT (c1) DO NOTHING ----- -insert nullablecols - ├── columns: - ├── arbiter indexes: secondary - ├── insert-mapping: - │ ├── i:7 => c1:1 - │ ├── i:7 => c2:2 - │ ├── i:7 => c3:3 - │ └── i:7 => rowid:4 - ├── cardinality: [0 - 0] - ├── volatile, mutations - └── upsert-distinct-on - ├── columns: i:7!null - ├── grouping columns: i:7!null - ├── key: (7) - └── select - ├── columns: k:6!null i:7!null c1:18 rowid:21 - ├── key: (6) - ├── fd: ()-->(18,21), (6)-->(7) - ├── left-join (hash) - │ ├── columns: k:6!null i:7!null c1:18 rowid:21 - │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ ├── key: (6,21) - │ ├── fd: (6)-->(7), (21)-->(18), (18)~~>(21) - │ ├── select - │ │ ├── columns: k:6!null i:7!null - │ │ ├── key: (6) - │ │ ├── fd: (6)-->(7) - │ │ ├── scan a - │ │ │ ├── columns: k:6!null i:7!null - │ │ │ ├── key: (6) - │ │ │ └── fd: (6)-->(7) - │ │ └── filters - │ │ ├── exists [subquery] - │ │ │ └── limit - │ │ │ ├── columns: k:12!null i:13!null f:14 s:15!null j:16 - │ │ │ ├── cardinality: [0 - 1] - │ │ │ ├── key: () - │ │ │ ├── fd: ()-->(12-16) - │ │ │ ├── scan a - │ │ │ │ ├── columns: k:12!null i:13!null f:14 s:15!null j:16 - │ │ │ │ ├── key: (12) - │ │ │ │ ├── fd: (12)-->(13-16), (13,15)-->(12,14,16), (13,14)~~>(12,15,16) - │ │ │ │ └── limit hint: 1.00 - │ │ │ └── 1 - │ │ └── k:6 > 0 [outer=(6), constraints=(/6: [/1 - ]; tight)] - │ ├── scan nullablecols - │ │ ├── columns: c1:18 rowid:21!null - │ │ ├── key: (21) - │ │ └── fd: (21)-->(18), (18)~~>(21) - │ └── filters - │ └── i:7 = c1:18 [outer=(7,18), constraints=(/7: (/NULL - ]; /18: (/NULL - ]), fd=(7)==(18), (18)==(7)] - └── filters - └── rowid:21 IS NULL [outer=(21), constraints=(/21: [/NULL - /NULL]; tight), fd=()-->(21)] - # EnsureUpsertDistinctOn case. norm expect=EliminateGroupByProject INSERT INTO nullablecols (rowid, c1, c2, c3) @@ -1010,45 +950,33 @@ insert xy │ └── column7:7 => y:2 ├── cardinality: [0 - 0] ├── volatile, mutations - └── project + └── limit ├── columns: y:5!null column7:7 ├── cardinality: [0 - 1] ├── key: () ├── fd: ()-->(5,7) - └── limit - ├── columns: y:5!null column7:7 x:8 - ├── cardinality: [0 - 1] - ├── key: () - ├── fd: ()-->(5,7,8) - ├── select - │ ├── columns: y:5!null column7:7 x:8 - │ ├── fd: ()-->(5,7,8) - │ ├── limit hint: 1.00 - │ ├── left-join (hash) - │ │ ├── columns: y:5!null column7:7 x:8 - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ ├── fd: ()-->(5,7) - │ │ ├── limit hint: 1.00 - │ │ ├── project - │ │ │ ├── columns: column7:7 y:5!null - │ │ │ ├── fd: ()-->(5,7) - │ │ │ ├── select - │ │ │ │ ├── columns: y:5!null - │ │ │ │ ├── fd: ()-->(5) - │ │ │ │ ├── scan xy - │ │ │ │ │ └── columns: y:5 - │ │ │ │ └── filters - │ │ │ │ └── y:5 = 0 [outer=(5), constraints=(/5: [/0 - /0]; tight), fd=()-->(5)] - │ │ │ └── projections - │ │ │ └── CAST(NULL AS INT8) [as=column7:7] - │ │ ├── scan xy - │ │ │ ├── columns: x:8!null - │ │ │ └── key: (8) - │ │ └── filters - │ │ └── y:5 = x:8 [outer=(5,8), constraints=(/5: (/NULL - ]; /8: (/NULL - ]), fd=(5)==(8), (8)==(5)] - │ └── filters - │ └── x:8 IS NULL [outer=(8), constraints=(/8: [/NULL - /NULL]; tight), fd=()-->(8)] - └── 1 + ├── anti-join (hash) + │ ├── columns: y:5!null column7:7 + │ ├── fd: ()-->(5,7) + │ ├── limit hint: 1.00 + │ ├── project + │ │ ├── columns: column7:7 y:5!null + │ │ ├── fd: ()-->(5,7) + │ │ ├── select + │ │ │ ├── columns: y:5!null + │ │ │ ├── fd: ()-->(5) + │ │ │ ├── scan xy + │ │ │ │ └── columns: y:5 + │ │ │ └── filters + │ │ │ └── y:5 = 0 [outer=(5), constraints=(/5: [/0 - /0]; tight), fd=()-->(5)] + │ │ └── projections + │ │ └── CAST(NULL AS INT8) [as=column7:7] + │ ├── scan xy + │ │ ├── columns: x:8!null + │ │ └── key: (8) + │ └── filters + │ └── y:5 = x:8 [outer=(5,8), constraints=(/5: (/NULL - ]; /8: (/NULL - ]), fd=(5)==(8), (8)==(5)] + └── 1 # EnsureUpsertDistinctOn should reduce non-nullable constant grouping column. norm expect=ReduceNotNullGroupingCols @@ -1125,32 +1053,26 @@ insert xy ├── grouping columns: y:5 ├── lax-key: (5) ├── fd: ()-->(5,7) - ├── select - │ ├── columns: y:5 column7:7 x:8 - │ ├── fd: ()-->(5,7,8) - │ ├── left-join (hash) - │ │ ├── columns: y:5 column7:7 x:8 - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) + ├── anti-join (hash) + │ ├── columns: y:5 column7:7 + │ ├── fd: ()-->(5,7) + │ ├── project + │ │ ├── columns: column7:7 y:5 │ │ ├── fd: ()-->(5,7) - │ │ ├── project - │ │ │ ├── columns: column7:7 y:5 - │ │ │ ├── fd: ()-->(5,7) - │ │ │ ├── select - │ │ │ │ ├── columns: y:5 - │ │ │ │ ├── fd: ()-->(5) - │ │ │ │ ├── scan xy - │ │ │ │ │ └── columns: y:5 - │ │ │ │ └── filters - │ │ │ │ └── y:5 IS NULL [outer=(5), constraints=(/5: [/NULL - /NULL]; tight), fd=()-->(5)] - │ │ │ └── projections - │ │ │ └── CAST(NULL AS INT8) [as=column7:7] - │ │ ├── scan xy - │ │ │ ├── columns: x:8!null - │ │ │ └── key: (8) - │ │ └── filters - │ │ └── y:5 = x:8 [outer=(5,8), constraints=(/5: (/NULL - ]; /8: (/NULL - ]), fd=(5)==(8), (8)==(5)] + │ │ ├── select + │ │ │ ├── columns: y:5 + │ │ │ ├── fd: ()-->(5) + │ │ │ ├── scan xy + │ │ │ │ └── columns: y:5 + │ │ │ └── filters + │ │ │ └── y:5 IS NULL [outer=(5), constraints=(/5: [/NULL - /NULL]; tight), fd=()-->(5)] + │ │ └── projections + │ │ └── CAST(NULL AS INT8) [as=column7:7] + │ ├── scan xy + │ │ ├── columns: x:8!null + │ │ └── key: (8) │ └── filters - │ └── x:8 IS NULL [outer=(8), constraints=(/8: [/NULL - /NULL]; tight), fd=()-->(8)] + │ └── y:5 = x:8 [outer=(5,8), constraints=(/5: (/NULL - ]; /8: (/NULL - ]), fd=(5)==(8), (8)==(5)] └── aggregations └── first-agg [as=column7:7, outer=(7)] └── column7:7 @@ -1752,54 +1674,42 @@ insert a │ └── column16:16 => j:5 ├── cardinality: [0 - 0] ├── volatile, mutations - └── project + └── limit ├── columns: i:8!null "?column?":13!null "?column?":14!null column15:15 column16:16 ├── cardinality: [0 - 1] ├── key: () ├── fd: ()-->(8,13-16) - └── limit - ├── columns: i:8!null "?column?":13!null "?column?":14!null column15:15 column16:16 i:18 s:20 - ├── cardinality: [0 - 1] - ├── key: () - ├── fd: ()-->(8,13-16,18,20) - ├── select - │ ├── columns: i:8!null "?column?":13!null "?column?":14!null column15:15 column16:16 i:18 s:20 - │ ├── fd: ()-->(8,13-16,20) - │ ├── limit hint: 1.00 - │ ├── left-join (hash) - │ │ ├── columns: i:8!null "?column?":13!null "?column?":14!null column15:15 column16:16 i:18 s:20 - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ ├── fd: ()-->(8,13-16) - │ │ ├── limit hint: 1.00 - │ │ ├── project - │ │ │ ├── columns: column15:15 column16:16 "?column?":13!null "?column?":14!null i:8!null - │ │ │ ├── fd: ()-->(8,13-16) - │ │ │ ├── select - │ │ │ │ ├── columns: i:8!null - │ │ │ │ ├── fd: ()-->(8) - │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: i:8!null - │ │ │ │ └── filters - │ │ │ │ └── i:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)] - │ │ │ └── projections - │ │ │ ├── CAST(NULL AS FLOAT8) [as=column15:15] - │ │ │ ├── CAST(NULL AS JSONB) [as=column16:16] - │ │ │ ├── 1 [as="?column?":13] - │ │ │ └── 'foo' [as="?column?":14] - │ │ ├── select - │ │ │ ├── columns: i:18!null s:20!null - │ │ │ ├── key: (18) - │ │ │ ├── fd: ()-->(20) - │ │ │ ├── scan a - │ │ │ │ ├── columns: i:18!null s:20!null - │ │ │ │ └── key: (18,20) - │ │ │ └── filters - │ │ │ └── s:20 = 'foo' [outer=(20), constraints=(/20: [/'foo' - /'foo']; tight), fd=()-->(20)] - │ │ └── filters - │ │ └── i:8 = i:18 [outer=(8,18), constraints=(/8: (/NULL - ]; /18: (/NULL - ]), fd=(8)==(18), (18)==(8)] - │ └── filters - │ └── s:20 IS NULL [outer=(20), constraints=(/20: [/NULL - /NULL]; tight), fd=()-->(20)] - └── 1 + ├── anti-join (hash) + │ ├── columns: i:8!null "?column?":13!null "?column?":14!null column15:15 column16:16 + │ ├── fd: ()-->(8,13-16) + │ ├── limit hint: 1.00 + │ ├── project + │ │ ├── columns: column15:15 column16:16 "?column?":13!null "?column?":14!null i:8!null + │ │ ├── fd: ()-->(8,13-16) + │ │ ├── select + │ │ │ ├── columns: i:8!null + │ │ │ ├── fd: ()-->(8) + │ │ │ ├── scan a + │ │ │ │ └── columns: i:8!null + │ │ │ └── filters + │ │ │ └── i:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)] + │ │ └── projections + │ │ ├── CAST(NULL AS FLOAT8) [as=column15:15] + │ │ ├── CAST(NULL AS JSONB) [as=column16:16] + │ │ ├── 1 [as="?column?":13] + │ │ └── 'foo' [as="?column?":14] + │ ├── select + │ │ ├── columns: i:18!null s:20!null + │ │ ├── key: (18) + │ │ ├── fd: ()-->(20) + │ │ ├── scan a + │ │ │ ├── columns: i:18!null s:20!null + │ │ │ └── key: (18,20) + │ │ └── filters + │ │ └── s:20 = 'foo' [outer=(20), constraints=(/20: [/'foo' - /'foo']; tight), fd=()-->(20)] + │ └── filters + │ └── i:8 = i:18 [outer=(8,18), constraints=(/8: (/NULL - ]; /18: (/NULL - ]), fd=(8)==(18), (18)==(8)] + └── 1 # -------------------------------------------------- # EliminateEnsureDistinctNoColumns @@ -2205,39 +2115,28 @@ insert a │ └── column11:11 => j:5 ├── cardinality: [0 - 0] ├── volatile, mutations - └── project + └── anti-join (hash) ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11 ├── cardinality: [0 - 2] ├── fd: ()-->(10,11) - └── select - ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11 i:13 s:15 - ├── cardinality: [0 - 2] - ├── fd: ()-->(10,11,15) - ├── left-join (hash) - │ ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11 i:13 s:15 - │ ├── cardinality: [2 - 2] - │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ ├── fd: ()-->(10,11) - │ ├── project - │ │ ├── columns: column10:10 column11:11 column1:7!null column2:8 column3:9 - │ │ ├── cardinality: [2 - 2] - │ │ ├── fd: ()-->(10,11) - │ │ ├── values - │ │ │ ├── columns: column1:7!null column2:8 column3:9 - │ │ │ ├── cardinality: [2 - 2] - │ │ │ ├── (1, NULL, NULL) - │ │ │ └── (1, NULL, NULL) - │ │ └── projections - │ │ ├── CAST(NULL AS FLOAT8) [as=column10:10] - │ │ └── CAST(NULL AS JSONB) [as=column11:11] - │ ├── scan a - │ │ ├── columns: i:13!null s:15!null - │ │ └── key: (13,15) - │ └── filters - │ ├── column2:8 = s:15 [outer=(8,15), constraints=(/8: (/NULL - ]; /15: (/NULL - ]), fd=(8)==(15), (15)==(8)] - │ └── column3:9 = i:13 [outer=(9,13), constraints=(/9: (/NULL - ]; /13: (/NULL - ]), fd=(9)==(13), (13)==(9)] - └── filters - └── s:15 IS NULL [outer=(15), constraints=(/15: [/NULL - /NULL]; tight), fd=()-->(15)] + ├── project + │ ├── columns: column10:10 column11:11 column1:7!null column2:8 column3:9 + │ ├── cardinality: [2 - 2] + │ ├── fd: ()-->(10,11) + │ ├── values + │ │ ├── columns: column1:7!null column2:8 column3:9 + │ │ ├── cardinality: [2 - 2] + │ │ ├── (1, NULL, NULL) + │ │ └── (1, NULL, NULL) + │ └── projections + │ ├── CAST(NULL AS FLOAT8) [as=column10:10] + │ └── CAST(NULL AS JSONB) [as=column11:11] + ├── scan a + │ ├── columns: i:13!null s:15!null + │ └── key: (13,15) + └── filters + ├── column2:8 = s:15 [outer=(8,15), constraints=(/8: (/NULL - ]; /15: (/NULL - ]), fd=(8)==(15), (15)==(8)] + └── column3:9 = i:13 [outer=(9,13), constraints=(/9: (/NULL - ]; /13: (/NULL - ]), fd=(9)==(13), (13)==(9)] # EnsureUpsertDistinctOn treats NULL values as distinct, so it can be eliminated. norm expect=EliminateDistinctOnValues @@ -2374,72 +2273,47 @@ insert a │ └── column11:11 => j:5 ├── cardinality: [0 - 0] ├── volatile, mutations - └── project + └── anti-join (hash) ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 ├── cardinality: [0 - 3] ├── fd: ()-->(11) - └── select - ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 i:19 s:21 i:25 f:26 - ├── cardinality: [0 - 3] - ├── fd: ()-->(11,12,21,25) - ├── left-join (hash) - │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 i:19 s:21 i:25 f:26 - │ ├── cardinality: [0 - 3] - │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ ├── fd: ()-->(11,12,21) - │ ├── select - │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 i:19 s:21 - │ │ ├── cardinality: [0 - 3] - │ │ ├── fd: ()-->(11,12,21) - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 i:19 s:21 - │ │ │ ├── cardinality: [0 - 3] - │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ ├── fd: ()-->(11,12) - │ │ │ ├── select - │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 - │ │ │ │ ├── cardinality: [0 - 3] - │ │ │ │ ├── fd: ()-->(11,12) - │ │ │ │ ├── left-join (hash) - │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 k:12 - │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ │ │ ├── fd: ()-->(11) - │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: column11:11 column1:7!null column2:8!null column3:9!null column4:10!null - │ │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ │ ├── fd: ()-->(11) - │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null - │ │ │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ │ │ ├── (1, 'foo', 1, 1.0) - │ │ │ │ │ │ │ ├── (2, 'bar', 2, 2.0) - │ │ │ │ │ │ │ └── (3, 'foo', 2, 1.0) - │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] - │ │ │ │ │ ├── scan a - │ │ │ │ │ │ ├── columns: k:12!null - │ │ │ │ │ │ └── key: (12) - │ │ │ │ │ └── filters - │ │ │ │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] - │ │ │ │ └── filters - │ │ │ │ └── k:12 IS NULL [outer=(12), constraints=(/12: [/NULL - /NULL]; tight), fd=()-->(12)] - │ │ │ ├── scan a - │ │ │ │ ├── columns: i:19!null s:21!null - │ │ │ │ └── key: (19,21) - │ │ │ └── filters - │ │ │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] - │ │ │ └── column3:9 = i:19 [outer=(9,19), constraints=(/9: (/NULL - ]; /19: (/NULL - ]), fd=(9)==(19), (19)==(9)] - │ │ └── filters - │ │ └── s:21 IS NULL [outer=(21), constraints=(/21: [/NULL - /NULL]; tight), fd=()-->(21)] - │ ├── scan a - │ │ ├── columns: i:25!null f:26 - │ │ └── lax-key: (25,26) - │ └── filters - │ ├── column4:10 = f:26 [outer=(10,26), constraints=(/10: (/NULL - ]; /26: (/NULL - ]), fd=(10)==(26), (26)==(10)] - │ └── column3:9 = i:25 [outer=(9,25), constraints=(/9: (/NULL - ]; /25: (/NULL - ]), fd=(9)==(25), (25)==(9)] - └── filters - └── i:25 IS NULL [outer=(25), constraints=(/25: [/NULL - /NULL]; tight), fd=()-->(25)] + ├── anti-join (hash) + │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 + │ ├── cardinality: [0 - 3] + │ ├── fd: ()-->(11) + │ ├── anti-join (hash) + │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column11:11 + │ │ ├── cardinality: [0 - 3] + │ │ ├── fd: ()-->(11) + │ │ ├── project + │ │ │ ├── columns: column11:11 column1:7!null column2:8!null column3:9!null column4:10!null + │ │ │ ├── cardinality: [3 - 3] + │ │ │ ├── fd: ()-->(11) + │ │ │ ├── values + │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null + │ │ │ │ ├── cardinality: [3 - 3] + │ │ │ │ ├── (1, 'foo', 1, 1.0) + │ │ │ │ ├── (2, 'bar', 2, 2.0) + │ │ │ │ └── (3, 'foo', 2, 1.0) + │ │ │ └── projections + │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] + │ │ ├── scan a + │ │ │ ├── columns: k:12!null + │ │ │ └── key: (12) + │ │ └── filters + │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] + │ ├── scan a + │ │ ├── columns: i:19!null s:21!null + │ │ └── key: (19,21) + │ └── filters + │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] + │ └── column3:9 = i:19 [outer=(9,19), constraints=(/9: (/NULL - ]; /19: (/NULL - ]), fd=(9)==(19), (19)==(9)] + ├── scan a + │ ├── columns: i:25!null f:26 + │ └── lax-key: (25,26) + └── filters + ├── column4:10 = f:26 [outer=(10,26), constraints=(/10: (/NULL - ]; /26: (/NULL - ]), fd=(10)==(26), (26)==(10)] + └── column3:9 = i:25 [outer=(9,25), constraints=(/9: (/NULL - ]; /25: (/NULL - ]), fd=(9)==(25), (25)==(9)] # DO NOTHING case where one distinct op can be removed (k), but two others # can't: (s, i) and (f, i). @@ -2462,86 +2336,64 @@ insert a ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 ├── grouping columns: column3:9!null column10:10 ├── cardinality: [0 - 3] - ├── lax-key: (9,10) + ├── lax-key: (8,10) ├── fd: ()-->(10,11), (8,10)~~>(7,9), (9,10)~~>(7,8,11) - ├── select - │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 i:25 f:26 + ├── anti-join (hash) + │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 │ ├── cardinality: [0 - 3] - │ ├── lax-key: (8,10,25,26) - │ ├── fd: ()-->(10,11,25), (8,10)~~>(7,9) - │ ├── left-join (hash) - │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 i:25 f:26 + │ ├── lax-key: (8,10) + │ ├── fd: ()-->(10,11), (8,10)~~>(7,9,11) + │ ├── upsert-distinct-on + │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 + │ │ ├── grouping columns: column2:8!null column10:10 │ │ ├── cardinality: [0 - 3] - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ ├── lax-key: (8,10,25,26) - │ │ ├── fd: ()-->(10,11), (8,10)~~>(7,9) - │ │ ├── upsert-distinct-on + │ │ ├── lax-key: (8,10) + │ │ ├── fd: ()-->(10,11), (8,10)~~>(7,9,11) + │ │ ├── anti-join (hash) │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 - │ │ │ ├── grouping columns: column2:8!null column10:10 │ │ │ ├── cardinality: [0 - 3] - │ │ │ ├── lax-key: (8,10) - │ │ │ ├── fd: ()-->(10,11), (8,10)~~>(7,9,11) - │ │ │ ├── select - │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 k:12 i:19 s:21 + │ │ │ ├── fd: ()-->(10,11) + │ │ │ ├── anti-join (hash) + │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 │ │ │ │ ├── cardinality: [0 - 3] - │ │ │ │ ├── fd: ()-->(10-12,21) - │ │ │ │ ├── left-join (hash) - │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 k:12 i:19 s:21 - │ │ │ │ │ ├── cardinality: [0 - 3] - │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ │ │ ├── fd: ()-->(10-12) - │ │ │ │ │ ├── select - │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 k:12 - │ │ │ │ │ │ ├── cardinality: [0 - 3] - │ │ │ │ │ │ ├── fd: ()-->(10-12) - │ │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11 k:12 - │ │ │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ │ │ │ │ ├── fd: ()-->(10,11) - │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ ├── columns: column10:10 column11:11 column1:7!null column2:8!null column3:9!null - │ │ │ │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ │ │ │ ├── fd: ()-->(10,11) - │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null - │ │ │ │ │ │ │ │ │ ├── cardinality: [3 - 3] - │ │ │ │ │ │ │ │ │ ├── (1, 'foo', 1.0) - │ │ │ │ │ │ │ │ │ ├── (2, 'bar', 2.0) - │ │ │ │ │ │ │ │ │ └── (3, 'foo', 1.0) - │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ ├── CAST(NULL AS INT8) [as=column10:10] - │ │ │ │ │ │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] - │ │ │ │ │ │ │ ├── scan a - │ │ │ │ │ │ │ │ ├── columns: k:12!null - │ │ │ │ │ │ │ │ └── key: (12) - │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] - │ │ │ │ │ │ └── filters - │ │ │ │ │ │ └── k:12 IS NULL [outer=(12), constraints=(/12: [/NULL - /NULL]; tight), fd=()-->(12)] - │ │ │ │ │ ├── scan a - │ │ │ │ │ │ ├── columns: i:19!null s:21!null - │ │ │ │ │ │ └── key: (19,21) - │ │ │ │ │ └── filters - │ │ │ │ │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] - │ │ │ │ │ └── column10:10 = i:19 [outer=(10,19), constraints=(/10: (/NULL - ]; /19: (/NULL - ]), fd=(10)==(19), (19)==(10)] + │ │ │ │ ├── fd: ()-->(10,11) + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: column10:10 column11:11 column1:7!null column2:8!null column3:9!null + │ │ │ │ │ ├── cardinality: [3 - 3] + │ │ │ │ │ ├── fd: ()-->(10,11) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null + │ │ │ │ │ │ ├── cardinality: [3 - 3] + │ │ │ │ │ │ ├── (1, 'foo', 1.0) + │ │ │ │ │ │ ├── (2, 'bar', 2.0) + │ │ │ │ │ │ └── (3, 'foo', 1.0) + │ │ │ │ │ └── projections + │ │ │ │ │ ├── CAST(NULL AS INT8) [as=column10:10] + │ │ │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] + │ │ │ │ ├── scan a + │ │ │ │ │ ├── columns: k:12!null + │ │ │ │ │ └── key: (12) │ │ │ │ └── filters - │ │ │ │ └── s:21 IS NULL [outer=(21), constraints=(/21: [/NULL - /NULL]; tight), fd=()-->(21)] - │ │ │ └── aggregations - │ │ │ ├── first-agg [as=column1:7, outer=(7)] - │ │ │ │ └── column1:7 - │ │ │ ├── first-agg [as=column3:9, outer=(9)] - │ │ │ │ └── column3:9 - │ │ │ └── first-agg [as=column11:11, outer=(11)] - │ │ │ └── column11:11 - │ │ ├── scan a - │ │ │ ├── columns: i:25!null f:26 - │ │ │ └── lax-key: (25,26) - │ │ └── filters - │ │ ├── column3:9 = f:26 [outer=(9,26), constraints=(/9: (/NULL - ]; /26: (/NULL - ]), fd=(9)==(26), (26)==(9)] - │ │ └── column10:10 = i:25 [outer=(10,25), constraints=(/10: (/NULL - ]; /25: (/NULL - ]), fd=(10)==(25), (25)==(10)] + │ │ │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] + │ │ │ ├── scan a + │ │ │ │ ├── columns: i:19!null s:21!null + │ │ │ │ └── key: (19,21) + │ │ │ └── filters + │ │ │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] + │ │ │ └── column10:10 = i:19 [outer=(10,19), constraints=(/10: (/NULL - ]; /19: (/NULL - ]), fd=(10)==(19), (19)==(10)] + │ │ └── aggregations + │ │ ├── first-agg [as=column1:7, outer=(7)] + │ │ │ └── column1:7 + │ │ ├── first-agg [as=column3:9, outer=(9)] + │ │ │ └── column3:9 + │ │ └── first-agg [as=column11:11, outer=(11)] + │ │ └── column11:11 + │ ├── scan a + │ │ ├── columns: i:25!null f:26 + │ │ └── lax-key: (25,26) │ └── filters - │ └── i:25 IS NULL [outer=(25), constraints=(/25: [/NULL - /NULL]; tight), fd=()-->(25)] + │ ├── column3:9 = f:26 [outer=(9,26), constraints=(/9: (/NULL - ]; /26: (/NULL - ]), fd=(9)==(26), (26)==(9)] + │ └── column10:10 = i:25 [outer=(10,25), constraints=(/10: (/NULL - ]; /25: (/NULL - ]), fd=(10)==(25), (25)==(10)] └── aggregations ├── first-agg [as=column1:7, outer=(7)] │ └── column1:7 @@ -2568,100 +2420,69 @@ insert a │ └── column11:11 => j:5 ├── cardinality: [0 - 0] ├── volatile, mutations - └── project + └── anti-join (hash) ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 ├── cardinality: [0 - 2] ├── volatile - ├── fd: ()-->(11), (7)~~>(8-10) - └── select - ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 i:19 s:21 i:25 f:26 - ├── cardinality: [0 - 2] - ├── volatile - ├── lax-key: (7,19,21,25,26) - ├── fd: ()-->(11,21,25), (7)~~>(8-10) - ├── left-join (hash) - │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 i:19 s:21 i:25 f:26 - │ ├── cardinality: [0 - 2] - │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ ├── volatile - │ ├── lax-key: (7,19,21,25,26) - │ ├── fd: ()-->(11,21), (7)~~>(8-10) - │ ├── select - │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 i:19 s:21 - │ │ ├── cardinality: [0 - 2] - │ │ ├── volatile - │ │ ├── lax-key: (7,19,21) - │ │ ├── fd: ()-->(11,21), (7)~~>(8-10) - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 i:19 s:21 - │ │ │ ├── cardinality: [0 - 2] - │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ ├── volatile - │ │ │ ├── lax-key: (7,19,21) - │ │ │ ├── fd: ()-->(11), (7)~~>(8-10) - │ │ │ ├── upsert-distinct-on - │ │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 - │ │ │ │ ├── grouping columns: column1:7 - │ │ │ │ ├── cardinality: [0 - 2] - │ │ │ │ ├── volatile - │ │ │ │ ├── lax-key: (7) - │ │ │ │ ├── fd: ()-->(11), (7)~~>(8-11) - │ │ │ │ ├── select - │ │ │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 k:12 - │ │ │ │ │ ├── cardinality: [0 - 2] - │ │ │ │ │ ├── volatile - │ │ │ │ │ ├── fd: ()-->(11,12) - │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 k:12 - │ │ │ │ │ │ ├── cardinality: [2 - 2] - │ │ │ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) - │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ ├── fd: ()-->(11) - │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: column11:11 column1:7 column2:8!null column3:9!null column4:10!null - │ │ │ │ │ │ │ ├── cardinality: [2 - 2] - │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ ├── fd: ()-->(11) - │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null - │ │ │ │ │ │ │ │ ├── cardinality: [2 - 2] - │ │ │ │ │ │ │ │ ├── volatile - │ │ │ │ │ │ │ │ ├── (unique_rowid(), 'foo', 1, 1.0) - │ │ │ │ │ │ │ │ └── (unique_rowid(), 'bar', 2, 2.0) - │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] - │ │ │ │ │ │ ├── scan a - │ │ │ │ │ │ │ ├── columns: k:12!null - │ │ │ │ │ │ │ └── key: (12) - │ │ │ │ │ │ └── filters - │ │ │ │ │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] - │ │ │ │ │ └── filters - │ │ │ │ │ └── k:12 IS NULL [outer=(12), constraints=(/12: [/NULL - /NULL]; tight), fd=()-->(12)] - │ │ │ │ └── aggregations - │ │ │ │ ├── first-agg [as=column2:8, outer=(8)] - │ │ │ │ │ └── column2:8 - │ │ │ │ ├── first-agg [as=column3:9, outer=(9)] - │ │ │ │ │ └── column3:9 - │ │ │ │ ├── first-agg [as=column4:10, outer=(10)] - │ │ │ │ │ └── column4:10 - │ │ │ │ └── first-agg [as=column11:11, outer=(11)] - │ │ │ │ └── column11:11 - │ │ │ ├── scan a - │ │ │ │ ├── columns: i:19!null s:21!null - │ │ │ │ └── key: (19,21) - │ │ │ └── filters - │ │ │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] - │ │ │ └── column3:9 = i:19 [outer=(9,19), constraints=(/9: (/NULL - ]; /19: (/NULL - ]), fd=(9)==(19), (19)==(9)] - │ │ └── filters - │ │ └── s:21 IS NULL [outer=(21), constraints=(/21: [/NULL - /NULL]; tight), fd=()-->(21)] - │ ├── scan a - │ │ ├── columns: i:25!null f:26 - │ │ └── lax-key: (25,26) - │ └── filters - │ ├── column4:10 = f:26 [outer=(10,26), constraints=(/10: (/NULL - ]; /26: (/NULL - ]), fd=(10)==(26), (26)==(10)] - │ └── column3:9 = i:25 [outer=(9,25), constraints=(/9: (/NULL - ]; /25: (/NULL - ]), fd=(9)==(25), (25)==(9)] - └── filters - └── i:25 IS NULL [outer=(25), constraints=(/25: [/NULL - /NULL]; tight), fd=()-->(25)] + ├── lax-key: (7) + ├── fd: ()-->(11), (7)~~>(8-11) + ├── anti-join (hash) + │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 + │ ├── cardinality: [0 - 2] + │ ├── volatile + │ ├── lax-key: (7) + │ ├── fd: ()-->(11), (7)~~>(8-11) + │ ├── upsert-distinct-on + │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 + │ │ ├── grouping columns: column1:7 + │ │ ├── cardinality: [0 - 2] + │ │ ├── volatile + │ │ ├── lax-key: (7) + │ │ ├── fd: ()-->(11), (7)~~>(8-11) + │ │ ├── anti-join (hash) + │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null column11:11 + │ │ │ ├── cardinality: [0 - 2] + │ │ │ ├── volatile + │ │ │ ├── fd: ()-->(11) + │ │ │ ├── project + │ │ │ │ ├── columns: column11:11 column1:7 column2:8!null column3:9!null column4:10!null + │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ ├── volatile + │ │ │ │ ├── fd: ()-->(11) + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:7 column2:8!null column3:9!null column4:10!null + │ │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ │ ├── volatile + │ │ │ │ │ ├── (unique_rowid(), 'foo', 1, 1.0) + │ │ │ │ │ └── (unique_rowid(), 'bar', 2, 2.0) + │ │ │ │ └── projections + │ │ │ │ └── CAST(NULL AS JSONB) [as=column11:11] + │ │ │ ├── scan a + │ │ │ │ ├── columns: k:12!null + │ │ │ │ └── key: (12) + │ │ │ └── filters + │ │ │ └── column1:7 = k:12 [outer=(7,12), constraints=(/7: (/NULL - ]; /12: (/NULL - ]), fd=(7)==(12), (12)==(7)] + │ │ └── aggregations + │ │ ├── first-agg [as=column2:8, outer=(8)] + │ │ │ └── column2:8 + │ │ ├── first-agg [as=column3:9, outer=(9)] + │ │ │ └── column3:9 + │ │ ├── first-agg [as=column4:10, outer=(10)] + │ │ │ └── column4:10 + │ │ └── first-agg [as=column11:11, outer=(11)] + │ │ └── column11:11 + │ ├── scan a + │ │ ├── columns: i:19!null s:21!null + │ │ └── key: (19,21) + │ └── filters + │ ├── column2:8 = s:21 [outer=(8,21), constraints=(/8: (/NULL - ]; /21: (/NULL - ]), fd=(8)==(21), (21)==(8)] + │ └── column3:9 = i:19 [outer=(9,19), constraints=(/9: (/NULL - ]; /19: (/NULL - ]), fd=(9)==(19), (19)==(9)] + ├── scan a + │ ├── columns: i:25!null f:26 + │ └── lax-key: (25,26) + └── filters + ├── column4:10 = f:26 [outer=(10,26), constraints=(/10: (/NULL - ]; /26: (/NULL - ]), fd=(10)==(26), (26)==(10)] + └── column3:9 = i:25 [outer=(9,25), constraints=(/9: (/NULL - ]; /25: (/NULL - ]), fd=(9)==(25), (25)==(9)] # DO NOTHING case with explicit conflict columns (only add upsert-distinct-on # for one index). @@ -2685,35 +2506,29 @@ insert a ├── grouping columns: i:8!null ├── key: (8) ├── fd: ()-->(13-15) - ├── select - │ ├── columns: i:8!null "?column?":13!null column14:14 column15:15 i:17 s:19 - │ ├── fd: ()-->(13-15,19) - │ ├── left-join (hash) - │ │ ├── columns: i:8!null "?column?":13!null column14:14 column15:15 i:17 s:19 - │ │ ├── multiplicity: left-rows(exactly-one), right-rows(one-or-more) + ├── anti-join (hash) + │ ├── columns: i:8!null "?column?":13!null column14:14 column15:15 + │ ├── fd: ()-->(13-15) + │ ├── project + │ │ ├── columns: column14:14 column15:15 "?column?":13!null i:8!null │ │ ├── fd: ()-->(13-15) - │ │ ├── project - │ │ │ ├── columns: column14:14 column15:15 "?column?":13!null i:8!null - │ │ │ ├── fd: ()-->(13-15) - │ │ │ ├── scan a - │ │ │ │ └── columns: i:8!null - │ │ │ └── projections - │ │ │ ├── CAST(NULL AS FLOAT8) [as=column14:14] - │ │ │ ├── CAST(NULL AS JSONB) [as=column15:15] - │ │ │ └── 'foo' [as="?column?":13] - │ │ ├── select + │ │ ├── scan a + │ │ │ └── columns: i:8!null + │ │ └── projections + │ │ ├── CAST(NULL AS FLOAT8) [as=column14:14] + │ │ ├── CAST(NULL AS JSONB) [as=column15:15] + │ │ └── 'foo' [as="?column?":13] + │ ├── select + │ │ ├── columns: i:17!null s:19!null + │ │ ├── key: (17) + │ │ ├── fd: ()-->(19) + │ │ ├── scan a │ │ │ ├── columns: i:17!null s:19!null - │ │ │ ├── key: (17) - │ │ │ ├── fd: ()-->(19) - │ │ │ ├── scan a - │ │ │ │ ├── columns: i:17!null s:19!null - │ │ │ │ └── key: (17,19) - │ │ │ └── filters - │ │ │ └── s:19 = 'foo' [outer=(19), constraints=(/19: [/'foo' - /'foo']; tight), fd=()-->(19)] + │ │ │ └── key: (17,19) │ │ └── filters - │ │ └── i:8 = i:17 [outer=(8,17), constraints=(/8: (/NULL - ]; /17: (/NULL - ]), fd=(8)==(17), (17)==(8)] + │ │ └── s:19 = 'foo' [outer=(19), constraints=(/19: [/'foo' - /'foo']; tight), fd=()-->(19)] │ └── filters - │ └── s:19 IS NULL [outer=(19), constraints=(/19: [/NULL - /NULL]; tight), fd=()-->(19)] + │ └── i:8 = i:17 [outer=(8,17), constraints=(/8: (/NULL - ]; /17: (/NULL - ]), fd=(8)==(17), (17)==(8)] └── aggregations ├── first-agg [as=column14:14, outer=(14)] │ └── column14:14 diff --git a/pkg/sql/opt/optbuilder/fk_cascade.go b/pkg/sql/opt/optbuilder/fk_cascade.go index 838dad59fa5f..4372e9ecbbae 100644 --- a/pkg/sql/opt/optbuilder/fk_cascade.go +++ b/pkg/sql/opt/optbuilder/fk_cascade.go @@ -547,7 +547,7 @@ func (b *Builder) buildDeleteCascadeMutationInput( )) } outScope.expr = b.factory.ConstructSemiJoin( - outScope.expr, mutationInput, on, &memo.JoinPrivate{}, + outScope.expr, mutationInput, on, memo.EmptyJoinPrivate, ) return outScope } @@ -855,7 +855,7 @@ func (b *Builder) buildUpdateCascadeMutationInput( // inner join is equivalent. // Note that this is very similar to the UPDATE ... FROM syntax. outScope.expr = f.ConstructInnerJoin( - outScope.expr, mutationInput, on, &memo.JoinPrivate{}, + outScope.expr, mutationInput, on, memo.EmptyJoinPrivate, ) // Append the columns from the right-hand side to the scope. for _, col := range outCols { diff --git a/pkg/sql/opt/optbuilder/insert.go b/pkg/sql/opt/optbuilder/insert.go index d55a1cb5f4c6..02942a62b824 100644 --- a/pkg/sql/opt/optbuilder/insert.go +++ b/pkg/sql/opt/optbuilder/insert.go @@ -160,8 +160,8 @@ func init() { // // If the ON CONFLICT clause contains a DO NOTHING clause, then each UNIQUE // index on the target table requires its own DISTINCT ON to ensure that the -// input has no duplicates, and its own LEFT OUTER JOIN to check whether a -// conflict exists. For example: +// input has no duplicates, and an ANTI JOIN to check whether a conflict exists. +// For example: // // CREATE TABLE ab (a INT PRIMARY KEY, b INT) // INSERT INTO ab (a, b) VALUES (1, 2), (1, 3) ON CONFLICT DO NOTHING @@ -170,9 +170,9 @@ func init() { // // SELECT x, y // FROM (SELECT DISTINCT ON (x) * FROM (VALUES (1, 2), (1, 3))) AS input(x, y) -// LEFT OUTER JOIN ab -// ON input.x = ab.a -// WHERE ab.a IS NULL +// WHERE NOT EXISTS( +// SELECT ab.a WHERE input.x = ab.a +// ) // // Note that an ordered input to the INSERT does not provide any guarantee about // the order in which mutations are applied, or the order of any returned rows @@ -287,9 +287,9 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope // Case 2: INSERT..ON CONFLICT DO NOTHING. case ins.OnConflict.DoNothing: - // Wrap the input in one LEFT OUTER JOIN per UNIQUE index, and filter out - // rows that have conflicts. See the buildInputForDoNothing comment for - // more details. + // Wrap the input in one ANTI JOIN per UNIQUE index, and filter out rows + // that have conflicts. See the buildInputForDoNothing comment for more + // details. conflictOrds := mb.mapPublicColumnNamesToOrdinals(ins.OnConflict.Columns) mb.buildInputForDoNothing(inScope, conflictOrds, ins.OnConflict.ArbiterPredicate) @@ -658,11 +658,9 @@ func (mb *mutationBuilder) buildInsert(returning tree.ReturningExprs) { mb.buildReturning(returning) } -// buildInputForDoNothing wraps the input expression in LEFT OUTER JOIN -// expressions, one for each UNIQUE index on the target table. It then adds a -// filter that discards rows that have a conflict (by checking a not-null table -// column to see if it was null-extended by the left join). See the comment -// header for Builder.buildInsert for an example. +// buildInputForDoNothing wraps the input expression in ANTI JOIN expressions, +// one for each UNIQUE index on the target table. See the comment header for +// Builder.buildInsert for an example. func (mb *mutationBuilder) buildInputForDoNothing( inScope *scope, conflictOrds util.FastIntSet, arbiterPredicate tree.Expr, ) { @@ -670,7 +668,6 @@ func (mb *mutationBuilder) buildInputForDoNothing( arbiterIndexes := mb.arbiterIndexes(conflictOrds, arbiterPredicate) mb.arbiters = arbiterIndexes.Ordered() - insertColSet := mb.outScope.expr.Relational().OutputCols insertColScope := mb.outScope.replace() insertColScope.appendColumnsFromScope(mb.outScope) @@ -678,8 +675,8 @@ func (mb *mutationBuilder) buildInputForDoNothing( // TODO(andyk): do we need to do more here? mb.outScope.ordering = nil - // Loop over each arbiter index, potentially creating a left join + filter - // for each one. + // Loop over each arbiter index, potentially creating an anti-join for each + // one. for idx, idxCount := 0, mb.tab.IndexCount(); idx < idxCount; idx++ { // Skip non-arbiter indexes. if !arbiterIndexes.Contains(idx) { @@ -693,7 +690,7 @@ func (mb *mutationBuilder) buildInputForDoNothing( predExpr = mb.parsePartialIndexPredicateExpr(idx) } - // Build the right side of the left outer join. Use a new metadata instance + // Build the right side of the anti-join. Use a new metadata instance // of the mutation table so that a different set of column IDs are used for // the two tables in the self-join. fetchScope := mb.b.buildScan( @@ -711,8 +708,8 @@ func (mb *mutationBuilder) buildInputForDoNothing( // If the index is a unique partial index, then rows that are not in the // partial index cannot conflict with insert rows. Therefore, a Select - // wraps the scan on the right side of the left outer join with the - // partial index predicate expression as the filter. + // wraps the scan on the right side of the anti-join with the partial + // index predicate expression as the filter. if isPartial { texpr := fetchScope.resolveAndRequireType(predExpr, types.Bool) predScalar := mb.b.buildScalar(texpr, fetchScope, nil, nil, nil) @@ -722,12 +719,6 @@ func (mb *mutationBuilder) buildInputForDoNothing( ) } - // Remember the column ID of a scan column that is not null. This will be - // used to detect whether a conflict was detected for a row. Such a column - // must always exist, since the index always contains the primary key - // columns, either explicitly or implicitly. - notNullColID := fetchScope.cols[findNotNullIndexCol(index)].id - // Build the join condition by creating a conjunction of equality conditions // that test each conflict column: // @@ -755,26 +746,12 @@ func (mb *mutationBuilder) buildInputForDoNothing( on = append(on, mb.b.factory.ConstructFiltersItem(predScalar)) } - // Construct the left join + filter. - // TODO(andyk): Convert this to use anti-join once we have support for - // lookup anti-joins. - mb.outScope.expr = mb.b.factory.ConstructProject( - mb.b.factory.ConstructSelect( - mb.b.factory.ConstructLeftJoin( - mb.outScope.expr, - fetchScope.expr, - on, - memo.EmptyJoinPrivate, - ), - memo.FiltersExpr{mb.b.factory.ConstructFiltersItem( - mb.b.factory.ConstructIs( - mb.b.factory.ConstructVariable(notNullColID), - memo.NullSingleton, - ), - )}, - ), - memo.EmptyProjectionsExpr, - insertColSet, + // Construct the anti-join. + mb.outScope.expr = mb.b.factory.ConstructAntiJoin( + mb.outScope.expr, + fetchScope.expr, + on, + memo.EmptyJoinPrivate, ) // If the index is a partial index, project a new column that allows the @@ -1180,9 +1157,9 @@ func (mb *mutationBuilder) projectUpsertColumns() { // arbiter indexes are found. // // Arbiter indexes ensure that the columns designated by conflictOrds reference -// at most one target row of a UNIQUE index. Using LEFT OUTER JOINs to detect -// conflicts relies upon this being true (otherwise result cardinality could -// increase). This is also a Postgres requirement. +// at most one target row of a UNIQUE index. Using ANTI JOINs and LEFT OUTER +// JOINs to detect conflicts relies upon this being true (otherwise result +// cardinality could increase). This is also a Postgres requirement. // // An arbiter index: // diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index e093861f4e5c..c3256216ae97 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -235,7 +235,7 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { } semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem(pkFilter)) - semiJoin := f.ConstructSemiJoin(checkInput, scanScope.expr, semiJoinFilters, &memo.JoinPrivate{}) + semiJoin := f.ConstructSemiJoin(checkInput, scanScope.expr, semiJoinFilters, memo.EmptyJoinPrivate) return f.ConstructUniqueChecksItem(semiJoin, &memo.UniqueChecksItemPrivate{ Table: h.mb.tabID, diff --git a/pkg/sql/opt/optbuilder/testdata/fk-checks-insert b/pkg/sql/opt/optbuilder/testdata/fk-checks-insert index 281c3ff72711..33f2ff951dd7 100644 --- a/pkg/sql/opt/optbuilder/testdata/fk-checks-insert +++ b/pkg/sql/opt/optbuilder/testdata/fk-checks-insert @@ -45,22 +45,16 @@ insert child ├── upsert-distinct-on │ ├── columns: column1:4!null column2:5!null │ ├── grouping columns: column1:4!null - │ ├── project + │ ├── anti-join (hash) │ │ ├── columns: column1:4!null column2:5!null - │ │ └── select - │ │ ├── columns: column1:4!null column2:5!null c:6 child.p:7 - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:4!null column2:5!null c:6 child.p:7 - │ │ │ ├── values - │ │ │ │ ├── columns: column1:4!null column2:5!null - │ │ │ │ ├── (100, 1) - │ │ │ │ └── (200, 1) - │ │ │ ├── scan child - │ │ │ │ └── columns: c:6!null child.p:7!null - │ │ │ └── filters - │ │ │ └── column1:4 = c:6 - │ │ └── filters - │ │ └── c:6 IS NULL + │ │ ├── values + │ │ │ ├── columns: column1:4!null column2:5!null + │ │ │ ├── (100, 1) + │ │ │ └── (200, 1) + │ │ ├── scan child + │ │ │ └── columns: c:6!null child.p:7!null + │ │ └── filters + │ │ └── column1:4 = c:6 │ └── aggregations │ └── first-agg [as=column2:5] │ └── column2:5 diff --git a/pkg/sql/opt/optbuilder/testdata/inverted-indexes b/pkg/sql/opt/optbuilder/testdata/inverted-indexes index 48792b322830..8000639f965e 100644 --- a/pkg/sql/opt/optbuilder/testdata/inverted-indexes +++ b/pkg/sql/opt/optbuilder/testdata/inverted-indexes @@ -95,21 +95,15 @@ insert kj └── upsert-distinct-on ├── columns: column1:5!null column2:6!null ├── grouping columns: column1:5!null - ├── project + ├── anti-join (hash) │ ├── columns: column1:5!null column2:6!null - │ └── select - │ ├── columns: column1:5!null column2:6!null k:7 j:8 - │ ├── left-join (hash) - │ │ ├── columns: column1:5!null column2:6!null k:7 j:8 - │ │ ├── values - │ │ │ ├── columns: column1:5!null column2:6!null - │ │ │ └── (1, '{"a": 2}') - │ │ ├── scan kj - │ │ │ └── columns: k:7!null j:8 - │ │ └── filters - │ │ └── column1:5 = k:7 - │ └── filters - │ └── k:7 IS NULL + │ ├── values + │ │ ├── columns: column1:5!null column2:6!null + │ │ └── (1, '{"a": 2}') + │ ├── scan kj + │ │ └── columns: k:7!null j:8 + │ └── filters + │ └── column1:5 = k:7 └── aggregations └── first-agg [as=column2:6] └── column2:6 diff --git a/pkg/sql/opt/optbuilder/testdata/upsert b/pkg/sql/opt/optbuilder/testdata/upsert index ddf33f1df1e5..02287fbb806d 100644 --- a/pkg/sql/opt/optbuilder/testdata/upsert +++ b/pkg/sql/opt/optbuilder/testdata/upsert @@ -541,62 +541,44 @@ insert xyz └── upsert-distinct-on ├── columns: column1:5!null column2:6!null column3:7!null ├── grouping columns: column2:6!null column3:7!null - ├── project + ├── anti-join (hash) │ ├── columns: column1:5!null column2:6!null column3:7!null - │ └── select - │ ├── columns: column1:5!null column2:6!null column3:7!null x:16 y:17 z:18 - │ ├── left-join (hash) - │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:16 y:17 z:18 - │ │ ├── upsert-distinct-on - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ ├── grouping columns: column2:6!null column3:7!null - │ │ │ ├── project - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ └── select - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:12 y:13 z:14 - │ │ │ │ ├── left-join (hash) - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:12 y:13 z:14 - │ │ │ │ │ ├── upsert-distinct-on - │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ ├── grouping columns: column1:5!null - │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ └── select - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:8 y:9 z:10 - │ │ │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:8 y:9 z:10 - │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ │ │ ├── (1, 2, 3) - │ │ │ │ │ │ │ │ │ └── (4, 5, 6) - │ │ │ │ │ │ │ │ ├── scan xyz - │ │ │ │ │ │ │ │ │ └── columns: x:8!null y:9 z:10 - │ │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ │ └── column1:5 = x:8 - │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── x:8 IS NULL - │ │ │ │ │ │ └── aggregations - │ │ │ │ │ │ ├── first-agg [as=column2:6] - │ │ │ │ │ │ │ └── column2:6 - │ │ │ │ │ │ └── first-agg [as=column3:7] - │ │ │ │ │ │ └── column3:7 - │ │ │ │ │ ├── scan xyz - │ │ │ │ │ │ └── columns: x:12!null y:13 z:14 - │ │ │ │ │ └── filters - │ │ │ │ │ ├── column2:6 = y:13 - │ │ │ │ │ └── column3:7 = z:14 - │ │ │ │ └── filters - │ │ │ │ └── x:12 IS NULL - │ │ │ └── aggregations - │ │ │ └── first-agg [as=column1:5] - │ │ │ └── column1:5 - │ │ ├── scan xyz - │ │ │ └── columns: x:16!null y:17 z:18 - │ │ └── filters - │ │ ├── column3:7 = z:18 - │ │ └── column2:6 = y:17 - │ └── filters - │ └── x:16 IS NULL + │ ├── upsert-distinct-on + │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ ├── grouping columns: column2:6!null column3:7!null + │ │ ├── anti-join (hash) + │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ ├── upsert-distinct-on + │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ ├── grouping columns: column1:5!null + │ │ │ │ ├── anti-join (hash) + │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ │ ├── (1, 2, 3) + │ │ │ │ │ │ └── (4, 5, 6) + │ │ │ │ │ ├── scan xyz + │ │ │ │ │ │ └── columns: x:8!null y:9 z:10 + │ │ │ │ │ └── filters + │ │ │ │ │ └── column1:5 = x:8 + │ │ │ │ └── aggregations + │ │ │ │ ├── first-agg [as=column2:6] + │ │ │ │ │ └── column2:6 + │ │ │ │ └── first-agg [as=column3:7] + │ │ │ │ └── column3:7 + │ │ │ ├── scan xyz + │ │ │ │ └── columns: x:12!null y:13 z:14 + │ │ │ └── filters + │ │ │ ├── column2:6 = y:13 + │ │ │ └── column3:7 = z:14 + │ │ └── aggregations + │ │ └── first-agg [as=column1:5] + │ │ └── column1:5 + │ ├── scan xyz + │ │ └── columns: x:16!null y:17 z:18 + │ └── filters + │ ├── column3:7 = z:18 + │ └── column2:6 = y:17 └── aggregations └── first-agg [as=column1:5] └── column1:5 @@ -617,23 +599,17 @@ insert xyz └── upsert-distinct-on ├── columns: column1:5!null column2:6!null column3:7!null ├── grouping columns: column2:6!null column3:7!null - ├── project + ├── anti-join (hash) │ ├── columns: column1:5!null column2:6!null column3:7!null - │ └── select - │ ├── columns: column1:5!null column2:6!null column3:7!null x:8 y:9 z:10 - │ ├── left-join (hash) - │ │ ├── columns: column1:5!null column2:6!null column3:7!null x:8 y:9 z:10 - │ │ ├── values - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ ├── (1, 2, 3) - │ │ │ └── (4, 5, 6) - │ │ ├── scan xyz - │ │ │ └── columns: x:8!null y:9 z:10 - │ │ └── filters - │ │ ├── column2:6 = y:9 - │ │ └── column3:7 = z:10 - │ └── filters - │ └── x:8 IS NULL + │ ├── values + │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ ├── (1, 2, 3) + │ │ └── (4, 5, 6) + │ ├── scan xyz + │ │ └── columns: x:8!null y:9 z:10 + │ └── filters + │ ├── column2:6 = y:9 + │ └── column3:7 = z:10 └── aggregations └── first-agg [as=column1:5] └── column1:5 @@ -1403,34 +1379,28 @@ insert checks ├── upsert-distinct-on │ ├── columns: column1:6!null column2:7!null column8:8 column9:9 │ ├── grouping columns: column1:6!null - │ ├── project + │ ├── anti-join (hash) │ │ ├── columns: column1:6!null column2:7!null column8:8 column9:9 - │ │ └── select - │ │ ├── columns: column1:6!null column2:7!null column8:8 column9:9 a:10 b:11 c:12 d:13 - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:6!null column2:7!null column8:8 column9:9 a:10 b:11 c:12 d:13 - │ │ │ ├── project - │ │ │ │ ├── columns: column9:9 column1:6!null column2:7!null column8:8 - │ │ │ │ ├── project - │ │ │ │ │ ├── columns: column8:8 column1:6!null column2:7!null - │ │ │ │ │ ├── values - │ │ │ │ │ │ ├── columns: column1:6!null column2:7!null - │ │ │ │ │ │ └── (1, 2) - │ │ │ │ │ └── projections - │ │ │ │ │ └── NULL::INT8 [as=column8:8] - │ │ │ │ └── projections - │ │ │ │ └── column8:8 + 1 [as=column9:9] - │ │ │ ├── scan checks - │ │ │ │ ├── columns: a:10!null b:11 c:12 d:13 - │ │ │ │ ├── check constraint expressions - │ │ │ │ │ └── a:10 > 0 - │ │ │ │ └── computed column expressions - │ │ │ │ └── d:13 - │ │ │ │ └── c:12 + 1 - │ │ │ └── filters - │ │ │ └── column1:6 = a:10 - │ │ └── filters - │ │ └── a:10 IS NULL + │ │ ├── project + │ │ │ ├── columns: column9:9 column1:6!null column2:7!null column8:8 + │ │ │ ├── project + │ │ │ │ ├── columns: column8:8 column1:6!null column2:7!null + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:6!null column2:7!null + │ │ │ │ │ └── (1, 2) + │ │ │ │ └── projections + │ │ │ │ └── NULL::INT8 [as=column8:8] + │ │ │ └── projections + │ │ │ └── column8:8 + 1 [as=column9:9] + │ │ ├── scan checks + │ │ │ ├── columns: a:10!null b:11 c:12 d:13 + │ │ │ ├── check constraint expressions + │ │ │ │ └── a:10 > 0 + │ │ │ └── computed column expressions + │ │ │ └── d:13 + │ │ │ └── c:12 + 1 + │ │ └── filters + │ │ └── column1:6 = a:10 │ └── aggregations │ ├── first-agg [as=column2:7] │ │ └── column2:7 @@ -1909,60 +1879,48 @@ insert partial_indexes ├── upsert-distinct-on │ ├── columns: column1:5!null column2:6!null column3:7!null │ ├── grouping columns: column2:6!null column3:7!null - │ ├── project + │ ├── anti-join (hash) │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ └── select - │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:12 b:13 c:14 - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:12 b:13 c:14 - │ │ │ ├── upsert-distinct-on - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ ├── grouping columns: column1:5!null - │ │ │ │ ├── project - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ └── select - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ └── (2, 1, 'bar') - │ │ │ │ │ │ ├── scan partial_indexes - │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10 - │ │ │ │ │ │ │ └── partial index predicates - │ │ │ │ │ │ │ ├── secondary: filters - │ │ │ │ │ │ │ │ └── c:10 = 'foo' - │ │ │ │ │ │ │ ├── secondary: filters - │ │ │ │ │ │ │ │ └── (a:8 > b:9) AND (c:10 = 'bar') - │ │ │ │ │ │ │ ├── b: filters - │ │ │ │ │ │ │ │ └── c:10 = 'delete-only' - │ │ │ │ │ │ │ └── b: filters - │ │ │ │ │ │ │ └── c:10 = 'write-only' - │ │ │ │ │ │ └── filters - │ │ │ │ │ │ └── column1:5 = a:8 - │ │ │ │ │ └── filters - │ │ │ │ │ └── a:8 IS NULL - │ │ │ │ └── aggregations - │ │ │ │ ├── first-agg [as=column2:6] - │ │ │ │ │ └── column2:6 - │ │ │ │ └── first-agg [as=column3:7] - │ │ │ │ └── column3:7 - │ │ │ ├── scan partial_indexes - │ │ │ │ ├── columns: a:12!null b:13 c:14 - │ │ │ │ └── partial index predicates - │ │ │ │ ├── secondary: filters - │ │ │ │ │ └── c:14 = 'foo' - │ │ │ │ ├── secondary: filters - │ │ │ │ │ └── (a:12 > b:13) AND (c:14 = 'bar') - │ │ │ │ ├── b: filters - │ │ │ │ │ └── c:14 = 'delete-only' - │ │ │ │ └── b: filters - │ │ │ │ └── c:14 = 'write-only' - │ │ │ └── filters - │ │ │ ├── column2:6 = b:13 - │ │ │ └── column3:7 = c:14 - │ │ └── filters - │ │ └── a:12 IS NULL + │ │ ├── upsert-distinct-on + │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ ├── grouping columns: column1:5!null + │ │ │ ├── anti-join (hash) + │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ └── (2, 1, 'bar') + │ │ │ │ ├── scan partial_indexes + │ │ │ │ │ ├── columns: a:8!null b:9 c:10 + │ │ │ │ │ └── partial index predicates + │ │ │ │ │ ├── secondary: filters + │ │ │ │ │ │ └── c:10 = 'foo' + │ │ │ │ │ ├── secondary: filters + │ │ │ │ │ │ └── (a:8 > b:9) AND (c:10 = 'bar') + │ │ │ │ │ ├── b: filters + │ │ │ │ │ │ └── c:10 = 'delete-only' + │ │ │ │ │ └── b: filters + │ │ │ │ │ └── c:10 = 'write-only' + │ │ │ │ └── filters + │ │ │ │ └── column1:5 = a:8 + │ │ │ └── aggregations + │ │ │ ├── first-agg [as=column2:6] + │ │ │ │ └── column2:6 + │ │ │ └── first-agg [as=column3:7] + │ │ │ └── column3:7 + │ │ ├── scan partial_indexes + │ │ │ ├── columns: a:12!null b:13 c:14 + │ │ │ └── partial index predicates + │ │ │ ├── secondary: filters + │ │ │ │ └── c:14 = 'foo' + │ │ │ ├── secondary: filters + │ │ │ │ └── (a:12 > b:13) AND (c:14 = 'bar') + │ │ │ ├── b: filters + │ │ │ │ └── c:14 = 'delete-only' + │ │ │ └── b: filters + │ │ │ └── c:14 = 'write-only' + │ │ └── filters + │ │ ├── column2:6 = b:13 + │ │ └── column3:7 = c:14 │ └── aggregations │ └── first-agg [as=column1:5] │ └── column1:5 @@ -2137,52 +2095,40 @@ insert unique_partial_indexes │ ├── grouping columns: column2:6!null upsert_partial_index_distinct1:16 │ ├── project │ │ ├── columns: upsert_partial_index_distinct1:16 column1:5!null column2:6!null column3:7!null - │ │ ├── project + │ │ ├── anti-join (hash) │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ └── select - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:12 b:13 c:14 - │ │ │ ├── left-join (hash) - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:12 b:13 c:14 - │ │ │ │ ├── upsert-distinct-on - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ ├── grouping columns: column1:5!null - │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ └── select - │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ │ └── (1, 1, 'bar') - │ │ │ │ │ │ │ ├── scan unique_partial_indexes - │ │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10 - │ │ │ │ │ │ │ │ └── partial index predicates - │ │ │ │ │ │ │ │ └── secondary: filters - │ │ │ │ │ │ │ │ └── c:10 = 'foo' - │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── column1:5 = a:8 - │ │ │ │ │ │ └── filters - │ │ │ │ │ │ └── a:8 IS NULL - │ │ │ │ │ └── aggregations - │ │ │ │ │ ├── first-agg [as=column2:6] - │ │ │ │ │ │ └── column2:6 - │ │ │ │ │ └── first-agg [as=column3:7] - │ │ │ │ │ └── column3:7 - │ │ │ │ ├── select - │ │ │ │ │ ├── columns: a:12!null b:13 c:14!null - │ │ │ │ │ ├── scan unique_partial_indexes - │ │ │ │ │ │ ├── columns: a:12!null b:13 c:14 - │ │ │ │ │ │ └── partial index predicates - │ │ │ │ │ │ └── secondary: filters - │ │ │ │ │ │ └── c:14 = 'foo' - │ │ │ │ │ └── filters - │ │ │ │ │ └── c:14 = 'foo' - │ │ │ │ └── filters - │ │ │ │ ├── column2:6 = b:13 - │ │ │ │ └── column3:7 = 'foo' - │ │ │ └── filters - │ │ │ └── a:12 IS NULL + │ │ │ ├── upsert-distinct-on + │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ ├── grouping columns: column1:5!null + │ │ │ │ ├── anti-join (hash) + │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ │ └── (1, 1, 'bar') + │ │ │ │ │ ├── scan unique_partial_indexes + │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10 + │ │ │ │ │ │ └── partial index predicates + │ │ │ │ │ │ └── secondary: filters + │ │ │ │ │ │ └── c:10 = 'foo' + │ │ │ │ │ └── filters + │ │ │ │ │ └── column1:5 = a:8 + │ │ │ │ └── aggregations + │ │ │ │ ├── first-agg [as=column2:6] + │ │ │ │ │ └── column2:6 + │ │ │ │ └── first-agg [as=column3:7] + │ │ │ │ └── column3:7 + │ │ │ ├── select + │ │ │ │ ├── columns: a:12!null b:13 c:14!null + │ │ │ │ ├── scan unique_partial_indexes + │ │ │ │ │ ├── columns: a:12!null b:13 c:14 + │ │ │ │ │ └── partial index predicates + │ │ │ │ │ └── secondary: filters + │ │ │ │ │ └── c:14 = 'foo' + │ │ │ │ └── filters + │ │ │ │ └── c:14 = 'foo' + │ │ │ └── filters + │ │ │ ├── column2:6 = b:13 + │ │ │ └── column3:7 = 'foo' │ │ └── projections │ │ └── (column3:7 = 'foo') OR NULL::BOOL [as=upsert_partial_index_distinct1:16] │ └── aggregations @@ -2218,67 +2164,55 @@ insert unique_partial_indexes │ ├── grouping columns: column2:6!null upsert_partial_index_distinct2:17 │ ├── project │ │ ├── columns: upsert_partial_index_distinct2:17 column1:5!null column2:6!null column3:7!null - │ │ ├── project + │ │ ├── anti-join (hash) │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ └── select - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:13 b:14 c:15 - │ │ │ ├── left-join (hash) - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:13 b:14 c:15 - │ │ │ │ ├── project - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ └── upsert-distinct-on - │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null upsert_partial_index_distinct1:12 - │ │ │ │ │ ├── grouping columns: column2:6!null upsert_partial_index_distinct1:12 - │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: upsert_partial_index_distinct1:12 column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ └── select - │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ │ │ ├── left-join (hash) - │ │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ │ │ │ │ │ └── (1, 1, 'bar') - │ │ │ │ │ │ │ │ ├── select - │ │ │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10!null - │ │ │ │ │ │ │ │ │ ├── scan unique_partial_indexes - │ │ │ │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10 - │ │ │ │ │ │ │ │ │ │ └── partial index predicates - │ │ │ │ │ │ │ │ │ │ ├── secondary: filters - │ │ │ │ │ │ │ │ │ │ │ └── c:10 = 'foo' - │ │ │ │ │ │ │ │ │ │ └── u2: filters - │ │ │ │ │ │ │ │ │ │ └── c:10 = 'bar' - │ │ │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ │ │ └── c:10 = 'foo' - │ │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ │ ├── column2:6 = b:9 - │ │ │ │ │ │ │ │ └── column3:7 = 'foo' - │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── a:8 IS NULL - │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── (column3:7 = 'foo') OR NULL::BOOL [as=upsert_partial_index_distinct1:12] - │ │ │ │ │ └── aggregations - │ │ │ │ │ ├── first-agg [as=column1:5] - │ │ │ │ │ │ └── column1:5 - │ │ │ │ │ └── first-agg [as=column3:7] - │ │ │ │ │ └── column3:7 - │ │ │ │ ├── select - │ │ │ │ │ ├── columns: a:13!null b:14 c:15!null - │ │ │ │ │ ├── scan unique_partial_indexes - │ │ │ │ │ │ ├── columns: a:13!null b:14 c:15 - │ │ │ │ │ │ └── partial index predicates - │ │ │ │ │ │ ├── secondary: filters - │ │ │ │ │ │ │ └── c:15 = 'foo' - │ │ │ │ │ │ └── u2: filters - │ │ │ │ │ │ └── c:15 = 'bar' - │ │ │ │ │ └── filters - │ │ │ │ │ └── c:15 = 'bar' - │ │ │ │ └── filters - │ │ │ │ ├── column2:6 = b:14 - │ │ │ │ └── column3:7 = 'bar' - │ │ │ └── filters - │ │ │ └── a:13 IS NULL + │ │ │ ├── project + │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ └── upsert-distinct-on + │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null upsert_partial_index_distinct1:12 + │ │ │ │ ├── grouping columns: column2:6!null upsert_partial_index_distinct1:12 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: upsert_partial_index_distinct1:12 column1:5!null column2:6!null column3:7!null + │ │ │ │ │ ├── anti-join (hash) + │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ │ │ │ │ └── (1, 1, 'bar') + │ │ │ │ │ │ ├── select + │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10!null + │ │ │ │ │ │ │ ├── scan unique_partial_indexes + │ │ │ │ │ │ │ │ ├── columns: a:8!null b:9 c:10 + │ │ │ │ │ │ │ │ └── partial index predicates + │ │ │ │ │ │ │ │ ├── secondary: filters + │ │ │ │ │ │ │ │ │ └── c:10 = 'foo' + │ │ │ │ │ │ │ │ └── u2: filters + │ │ │ │ │ │ │ │ └── c:10 = 'bar' + │ │ │ │ │ │ │ └── filters + │ │ │ │ │ │ │ └── c:10 = 'foo' + │ │ │ │ │ │ └── filters + │ │ │ │ │ │ ├── column2:6 = b:9 + │ │ │ │ │ │ └── column3:7 = 'foo' + │ │ │ │ │ └── projections + │ │ │ │ │ └── (column3:7 = 'foo') OR NULL::BOOL [as=upsert_partial_index_distinct1:12] + │ │ │ │ └── aggregations + │ │ │ │ ├── first-agg [as=column1:5] + │ │ │ │ │ └── column1:5 + │ │ │ │ └── first-agg [as=column3:7] + │ │ │ │ └── column3:7 + │ │ │ ├── select + │ │ │ │ ├── columns: a:13!null b:14 c:15!null + │ │ │ │ ├── scan unique_partial_indexes + │ │ │ │ │ ├── columns: a:13!null b:14 c:15 + │ │ │ │ │ └── partial index predicates + │ │ │ │ │ ├── secondary: filters + │ │ │ │ │ │ └── c:15 = 'foo' + │ │ │ │ │ └── u2: filters + │ │ │ │ │ └── c:15 = 'bar' + │ │ │ │ └── filters + │ │ │ │ └── c:15 = 'bar' + │ │ │ └── filters + │ │ │ ├── column2:6 = b:14 + │ │ │ └── column3:7 = 'bar' │ │ └── projections │ │ └── (column3:7 = 'bar') OR NULL::BOOL [as=upsert_partial_index_distinct2:17] │ └── aggregations @@ -2311,26 +2245,20 @@ insert unique_partial_indexes ├── upsert-distinct-on │ ├── columns: column1:5!null column2:6!null column3:7!null │ ├── grouping columns: column2:6!null - │ ├── project + │ ├── anti-join (hash) │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ └── select - │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 c:10 - │ │ │ ├── values - │ │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ │ └── (1, 1, 'bar') - │ │ │ ├── scan unique_partial_indexes - │ │ │ │ ├── columns: a:8!null b:9 c:10 - │ │ │ │ └── partial index predicates - │ │ │ │ ├── secondary: filters - │ │ │ │ │ └── c:10 = 'foo' - │ │ │ │ └── u2: filters - │ │ │ │ └── c:10 = 'bar' - │ │ │ └── filters - │ │ │ └── column2:6 = b:9 - │ │ └── filters - │ │ └── a:8 IS NULL + │ │ ├── values + │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ │ └── (1, 1, 'bar') + │ │ ├── scan unique_partial_indexes + │ │ │ ├── columns: a:8!null b:9 c:10 + │ │ │ └── partial index predicates + │ │ │ ├── secondary: filters + │ │ │ │ └── c:10 = 'foo' + │ │ │ └── u2: filters + │ │ │ └── c:10 = 'bar' + │ │ └── filters + │ │ └── column2:6 = b:9 │ └── aggregations │ ├── first-agg [as=column1:5] │ │ └── column1:5 @@ -2370,22 +2298,18 @@ insert unique_partial_indexes ├── partial index put columns: partial_index_put1:13 partial_index_put2:14 partial_index_put3:15 └── project ├── columns: partial_index_put1:13!null partial_index_put2:14!null partial_index_put3:15!null column1:5!null column2:6!null column3:7!null - ├── select - │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 - │ ├── right-join (cross) - │ │ ├── columns: column1:5!null column2:6!null column3:7!null a:8 b:9 - │ │ ├── select - │ │ │ ├── columns: a:8!null b:9!null - │ │ │ ├── scan unique_partial_indexes@u4,partial - │ │ │ │ └── columns: a:8!null b:9 - │ │ │ └── filters - │ │ │ └── b:9 = 1 - │ │ ├── values - │ │ │ ├── columns: column1:5!null column2:6!null column3:7!null - │ │ │ └── (1, 1, 'bar') - │ │ └── filters (true) - │ └── filters - │ └── a:8 IS NULL + ├── anti-join (cross) + │ ├── columns: column1:5!null column2:6!null column3:7!null + │ ├── values + │ │ ├── columns: column1:5!null column2:6!null column3:7!null + │ │ └── (1, 1, 'bar') + │ ├── select + │ │ ├── columns: b:9!null + │ │ ├── scan unique_partial_indexes@u4,partial + │ │ │ └── columns: b:9 + │ │ └── filters + │ │ └── b:9 = 1 + │ └── filters (true) └── projections ├── column3:7 = 'foo' [as=partial_index_put1:13] ├── column3:7 = 'bar' [as=partial_index_put2:14] diff --git a/pkg/sql/opt/xform/testdata/rules/groupby b/pkg/sql/opt/xform/testdata/rules/groupby index 4dbcb000bd0c..c23c068b2cec 100644 --- a/pkg/sql/opt/xform/testdata/rules/groupby +++ b/pkg/sql/opt/xform/testdata/rules/groupby @@ -1794,67 +1794,57 @@ memo (optimized, ~27KB, required=[presentation: w:10] [ordering: +11,+1]) memo INSERT INTO xyz SELECT v, w, 1.0 FROM kuvw ON CONFLICT (x) DO NOTHING ---- -memo (optimized, ~19KB, required=[]) +memo (optimized, ~21KB, required=[]) ├── G1: (insert G2 G3 G4 xyz) │ └── [] │ ├── best: (insert G2 G3 G4 xyz) - │ └── cost: 2158.51 - ├── G2: (upsert-distinct-on G5 G6 cols=(7)) (upsert-distinct-on G5 G6 cols=(7),ordering=+7 opt(10,11)) + │ └── cost: 2147.99 + ├── G2: (upsert-distinct-on G5 G6 cols=(7)) (upsert-distinct-on G5 G6 cols=(7),ordering=+7 opt(10)) │ └── [] - │ ├── best: (upsert-distinct-on G5="[ordering: +7 opt(10,11)]" G6 cols=(7),ordering=+7 opt(10,11)) - │ └── cost: 2158.50 + │ ├── best: (upsert-distinct-on G5 G6 cols=(7)) + │ └── cost: 2147.98 ├── G3: (unique-checks) ├── G4: (f-k-checks) - ├── G5: (select G7 G8) - │ ├── [ordering: +7 opt(10,11)] - │ │ ├── best: (select G7="[ordering: +7 opt(10,11)]" G8) - │ │ └── cost: 2158.08 - │ └── [] - │ ├── best: (select G7 G8) - │ └── cost: 2158.08 - ├── G6: (aggregations G9 G10) - ├── G7: (left-join G11 G12 G13) (right-join G12 G11 G13) (merge-join G11 G12 G14 left-join,+7,+11) (lookup-join G11 G14 xyz,keyCols=[7],outCols=(7,8,10,11)) (lookup-join G11 G14 xyz@xy,keyCols=[7],outCols=(7,8,10,11)) (merge-join G12 G11 G14 right-join,+11,+7) - │ ├── [ordering: +7 opt(10,11)] - │ │ ├── best: (merge-join G11="[ordering: +7 opt(10)]" G12="[ordering: +11]" G14 left-join,+7,+11) - │ │ └── cost: 2148.06 - │ └── [] - │ ├── best: (merge-join G11="[ordering: +7 opt(10)]" G12="[ordering: +11]" G14 left-join,+7,+11) - │ └── cost: 2148.06 - ├── G8: (filters G15) - ├── G9: (first-agg G16) - ├── G10: (first-agg G17) - ├── G11: (project G18 G19 v w) + ├── G5: (anti-join G7 G8 G9) (merge-join G7 G8 G10 anti-join,+7,+11) (lookup-join G7 G10 xyz,keyCols=[7],outCols=(7,8,10,11)) (lookup-join G7 G10 xyz@xy,keyCols=[7],outCols=(7,8,10,11)) + │ ├── [ordering: +7 opt(10)] + │ │ ├── best: (merge-join G7="[ordering: +7 opt(10)]" G8="[ordering: +11]" G10 anti-join,+7,+11) + │ │ └── cost: 2147.96 + │ └── [] + │ ├── best: (merge-join G7="[ordering: +7 opt(10)]" G8="[ordering: +11]" G10 anti-join,+7,+11) + │ └── cost: 2147.96 + ├── G6: (aggregations G11 G12) + ├── G7: (project G13 G14 v w) │ ├── [ordering: +7 opt(10)] - │ │ ├── best: (project G18="[ordering: +7]" G19 v w) + │ │ ├── best: (project G13="[ordering: +7]" G14 v w) │ │ └── cost: 1084.03 │ └── [] - │ ├── best: (project G18 G19 v w) + │ ├── best: (project G13 G14 v w) │ └── cost: 1084.03 - ├── G12: (scan xyz,cols=(11)) (scan xyz@xy,cols=(11)) (scan xyz@zyx,cols=(11)) (scan xyz@yy,cols=(11)) + ├── G8: (scan xyz,cols=(11)) (scan xyz@xy,cols=(11)) (scan xyz@zyx,cols=(11)) (scan xyz@yy,cols=(11)) │ ├── [ordering: +11] │ │ ├── best: (scan xyz@xy,cols=(11)) │ │ └── cost: 1034.02 │ └── [] │ ├── best: (scan xyz@xy,cols=(11)) │ └── cost: 1034.02 - ├── G13: (filters G20) - ├── G14: (filters) - ├── G15: (is G21 G22) - ├── G16: (variable w) - ├── G17: (variable "?column?") - ├── G18: (scan kuvw,cols=(7,8)) (scan kuvw@uvw,cols=(7,8)) (scan kuvw@wvu,cols=(7,8)) (scan kuvw@vw,cols=(7,8)) (scan kuvw@w,cols=(7,8)) + ├── G9: (filters G15) + ├── G10: (filters) + ├── G11: (first-agg G16) + ├── G12: (first-agg G17) + ├── G13: (scan kuvw,cols=(7,8)) (scan kuvw@uvw,cols=(7,8)) (scan kuvw@wvu,cols=(7,8)) (scan kuvw@vw,cols=(7,8)) (scan kuvw@w,cols=(7,8)) │ ├── [ordering: +7] │ │ ├── best: (scan kuvw@vw,cols=(7,8)) │ │ └── cost: 1064.02 │ └── [] │ ├── best: (scan kuvw,cols=(7,8)) │ └── cost: 1064.02 - ├── G19: (projections G23) - ├── G20: (eq G24 G21) - ├── G21: (variable x) - ├── G22: (null) - ├── G23: (const 1.0) - └── G24: (variable v) + ├── G14: (projections G18) + ├── G15: (eq G19 G20) + ├── G16: (variable w) + ├── G17: (variable "?column?") + ├── G18: (const 1.0) + ├── G19: (variable v) + └── G20: (variable x) # Ensure that streaming ensure-upsert-distinct-on will be used. memo