Commit ab412e15 authored by Drew Kimball's avatar Drew Kimball

opt: change JoinMultiplicity from a Relational prop to a join field

Previously, the JoinMultiplicity property was stored as a Relational
prop. This is a problem because all expressions in a memo group share
the same Relational props during exploration, and there are exploration
rules that can flip a join's left/right multiplicity.

This patch instead stores JoinMultiplcity as a join field that is
initialized during construction of the join. This fixes the shared
Relational props issue, and also  makes it possible for JoinMultiplicity
to aid in calculating other logical properties.

Fixes #49821

Release note: None
parent 994d3064
......@@ -948,6 +948,7 @@ TABLE t
└── k int not null
inner-join (hash)
├── columns: a:1 b:2 k:4 v:5
├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
├── stats: [rows=990, distinct(1)=99, null(1)=0, distinct(4)=99, null(4)=0]
├── cost: 2219.95
├── fd: (4)-->(5), (1)==(4), (4)==(1)
......@@ -957,7 +958,8 @@ inner-join (hash)
│ ├── stats: [rows=1000, distinct(1)=100, null(1)=10]
│ ├── cost: 1100.02
│ ├── prune: (1,2)
│ └── interesting orderings: (+1)
│ ├── interesting orderings: (+1)
│ └── unfiltered-cols: (1,2)
├── scan t
│ ├── columns: k:4 v:5
│ ├── stats: [rows=1000, distinct(4)=1000, null(4)=0]
......@@ -965,7 +967,8 @@ inner-join (hash)
│ ├── key: (4)
│ ├── fd: (4)-->(5)
│ ├── prune: (4,5)
│ └── interesting orderings: (+4)
│ ├── interesting orderings: (+4)
│ └── unfiltered-cols: (4,5)
└── filters
└── k:4 = a:1 [outer=(1,4), constraints=(/1: (/NULL - ]; /4: (/NULL - ]), fd=(1)==(4), (4)==(1)]
......
......@@ -421,6 +421,18 @@ func (jf JoinFlags) String() string {
return b.String()
}
func (ij *InnerJoinExpr) initUnexportedFields(mem *Memo) {
initJoinMultiplicity(ij)
}
func (lj *LeftJoinExpr) initUnexportedFields(mem *Memo) {
initJoinMultiplicity(lj)
}
func (fj *FullJoinExpr) initUnexportedFields(mem *Memo) {
initJoinMultiplicity(fj)
}
func (lj *LookupJoinExpr) initUnexportedFields(mem *Memo) {
// lookupProps are initialized as necessary by the logical props builder.
}
......@@ -434,6 +446,42 @@ func (zj *ZigzagJoinExpr) initUnexportedFields(mem *Memo) {
// builder.
}
// joinWithMultiplicity allows join operators for which JoinMultiplicity is
// supported (currently InnerJoin, LeftJoin, and FullJoin) to be treated
// polymorphically.
type joinWithMultiplicity interface {
setMultiplicity(props.JoinMultiplicity)
getMultiplicity() props.JoinMultiplicity
}
var _ joinWithMultiplicity = &InnerJoinExpr{}
var _ joinWithMultiplicity = &LeftJoinExpr{}
var _ joinWithMultiplicity = &FullJoinExpr{}
func (ij *InnerJoinExpr) setMultiplicity(multiplicity props.JoinMultiplicity) {
ij.multiplicity = multiplicity
}
func (ij *InnerJoinExpr) getMultiplicity() props.JoinMultiplicity {
return ij.multiplicity
}
func (lj *LeftJoinExpr) setMultiplicity(multiplicity props.JoinMultiplicity) {
lj.multiplicity = multiplicity
}
func (lj *LeftJoinExpr) getMultiplicity() props.JoinMultiplicity {
return lj.multiplicity
}
func (fj *FullJoinExpr) setMultiplicity(multiplicity props.JoinMultiplicity) {
fj.multiplicity = multiplicity
}
func (fj *FullJoinExpr) getMultiplicity() props.JoinMultiplicity {
return fj.multiplicity
}
// WindowFrame denotes the definition of a window frame for an individual
// window function, excluding the OFFSET expressions, if present.
type WindowFrame struct {
......
......@@ -605,6 +605,13 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
}
}
if join, ok := e.(joinWithMultiplicity); ok {
mult := join.getMultiplicity()
if s := mult.String(); s != "" {
tp.Childf("multiplicity: %s", s)
}
}
f.Buffer.Reset()
writeFlag := func(name string) {
if f.Buffer.Len() != 0 {
......@@ -690,11 +697,8 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
if r.JoinSize > 1 {
tp.Childf("join-size: %d", r.JoinSize)
}
switch e.Op() {
case opt.InnerJoinOp, opt.LeftJoinOp, opt.FullJoinOp:
if s := r.MultiplicityProps.String(); (r.Available&props.MultiplicityProps) != 0 && s != "" {
tp.Childf("multiplicity: %s", s)
}
if !r.UnfilteredCols.Empty() {
tp.Childf("unfiltered-cols: %s", r.UnfilteredCols.String())
}
if withUses := relational.Shared.Rule.WithUses; len(withUses) > 0 {
n := tp.Childf("cte-uses")
......
......@@ -18,67 +18,48 @@ import (
"github.com/cockroachdb/errors"
)
// DeriveJoinMultiplicity returns a JoinMultiplicity struct that describes how a
// join operator will affect the rows of its left and right inputs (e.g.
// duplicated and/or filtered). When the function is called on an operator other
// than an InnerJoin, a LeftJoin, or a FullJoin, it simply populates the
// UnfilteredCols field of the JoinMultiplicity for that operator and leaves the
// join fields unchanged.
//
// DeriveJoinMultiplicity recursively derives the UnfilteredCols field and
// populates the props.Relational.Rule.MultiplicityProps field as it goes to
// make future calls faster.
func DeriveJoinMultiplicity(in RelExpr) props.JoinMultiplicity {
// If the MultiplicityProps property has already been derived, return it
// immediately.
relational := in.Relational()
if relational.IsAvailable(props.MultiplicityProps) {
return relational.Rule.MultiplicityProps
}
relational.Rule.Available |= props.MultiplicityProps
var multiplicity props.JoinMultiplicity
// Derive MultiplicityProps now.
// initJoinMultiplicity initializes a JoinMultiplicity for the given InnerJoin,
// LeftJoin or FullJoin and returns it. initJoinMultiplicity should only be
// called during construction of the join by the initUnexportedFields methods.
// Panics if called on an operator other than an InnerJoin, LeftJoin, or
// FullJoin.
func initJoinMultiplicity(in RelExpr) {
switch t := in.(type) {
case *ScanExpr:
// All un-limited, unconstrained output columns are unfiltered columns.
if t.HardLimit == 0 && t.Constraint == nil {
multiplicity.UnfilteredCols = relational.OutputCols
}
case *ProjectExpr:
// Project never filters rows, so it passes through unfiltered columns.
unfilteredCols := DeriveJoinMultiplicity(t.Input).UnfilteredCols
multiplicity.UnfilteredCols = unfilteredCols.Intersection(relational.OutputCols)
case *InnerJoinExpr, *LeftJoinExpr, *FullJoinExpr:
// Calculate JoinMultiplicity.
left := t.Child(0).(RelExpr)
right := t.Child(1).(RelExpr)
filters := *t.Child(2).(*FiltersExpr)
multiplicity = GetJoinMultiplicityFromInputs(t.Op(), left, right, filters)
// Use the JoinMultiplicity to determine whether unfiltered columns can be
// passed through.
if multiplicity.JoinPreservesLeftRows() {
multiplicity.UnfilteredCols.UnionWith(DeriveJoinMultiplicity(left).UnfilteredCols)
}
if multiplicity.JoinPreservesRightRows() {
multiplicity.UnfilteredCols.UnionWith(DeriveJoinMultiplicity(right).UnfilteredCols)
}
multiplicity := DeriveJoinMultiplicityFromInputs(t.Op(), left, right, filters)
t.(joinWithMultiplicity).setMultiplicity(multiplicity)
default:
// An empty JoinMultiplicity is returned.
panic(errors.AssertionFailedf("invalid operator type: %v", t.Op()))
}
relational.Rule.MultiplicityProps = multiplicity
return relational.Rule.MultiplicityProps
}
// GetJoinMultiplicityFromInputs returns a JoinMultiplicity that describes how a
// join of the given type with the given inputs and filters will affect the rows
// of its inputs. When possible, DeriveJoinMultiplicity should be called instead
// because GetJoinMultiplicityFromInputs cannot take advantage of a previously
// calculated JoinMultiplicity.
func GetJoinMultiplicityFromInputs(
// GetJoinMultiplicity returns a JoinMultiplicity struct that describes how a
// join operator will affect the rows of its left and right inputs (e.g.
// duplicated and/or filtered). Panics if the method is called on an operator
// that does not support JoinMultiplicity (any operator other than an InnerJoin,
// LeftJoin, or FullJoin).
func GetJoinMultiplicity(in RelExpr) props.JoinMultiplicity {
if join, ok := in.(joinWithMultiplicity); ok {
// JoinMultiplicity has already been initialized during construction of the
// join, so simply return it.
return join.getMultiplicity()
}
panic(errors.AssertionFailedf("invalid operator type: %v", in.Op()))
}
// DeriveJoinMultiplicityFromInputs returns a JoinMultiplicity that describes
// how a join of the given type with the given inputs and filters will affect
// the rows of its inputs. When possible, GetJoinMultiplicity should be called
// instead because DeriveJoinMultiplicityFromInputs cannot take advantage of a
// previously calculated JoinMultiplicity. The UnfilteredCols Relational
// property is used in calculating the JoinMultiplicity, and is lazily derived
// by a call to deriveUnfilteredCols.
func DeriveJoinMultiplicityFromInputs(
joinOp opt.Operator, left, right RelExpr, filters FiltersExpr,
) props.JoinMultiplicity {
......@@ -101,6 +82,53 @@ func GetJoinMultiplicityFromInputs(
}
}
// deriveUnfilteredCols recursively derives the UnfilteredCols field and
// populates the props.Relational.Rule.UnfilteredCols field as it goes to
// make future calls faster.
func deriveUnfilteredCols(in RelExpr) opt.ColSet {
// If the UnfilteredCols property has already been derived, return it
// immediately.
relational := in.Relational()
if relational.IsAvailable(props.UnfilteredCols) {
return relational.Rule.UnfilteredCols
}
relational.Rule.Available |= props.UnfilteredCols
unfilteredCols := opt.ColSet{}
// Derive UnfilteredCols now.
switch t := in.(type) {
case *ScanExpr:
// All un-limited, unconstrained output columns are unfiltered columns.
if t.HardLimit == 0 && t.Constraint == nil {
unfilteredCols.UnionWith(relational.OutputCols)
}
case *ProjectExpr:
// Project never filters rows, so it passes through unfiltered columns.
unfilteredCols.UnionWith(deriveUnfilteredCols(t.Input).Intersection(relational.OutputCols))
case *InnerJoinExpr, *LeftJoinExpr, *FullJoinExpr:
left := t.Child(0).(RelExpr)
right := t.Child(1).(RelExpr)
filters := *t.Child(2).(*FiltersExpr)
multiplicity := DeriveJoinMultiplicityFromInputs(t.Op(), left, right, filters)
// Use the UnfilteredCols to determine whether unfiltered columns can be
// passed through.
if multiplicity.JoinPreservesLeftRows() {
unfilteredCols.UnionWith(deriveUnfilteredCols(left))
}
if multiplicity.JoinPreservesRightRows() {
unfilteredCols.UnionWith(deriveUnfilteredCols(right))
}
default:
// An empty ColSet is returned.
}
relational.Rule.UnfilteredCols = unfilteredCols
return relational.Rule.UnfilteredCols
}
// getJoinLeftMultiplicityVal returns a MultiplicityValue that describes whether
// a join with the given properties would duplicate or filter the rows of its
// left input.
......@@ -217,11 +245,11 @@ func filtersMatchAllLeftRows(left, right RelExpr, filters FiltersExpr) bool {
// Case 1b: if there is at least one not-null foreign key column referencing
// the unfiltered right columns, return true. Otherwise, false.
return makeForeignKeyMap(
md, left.Relational().NotNullCols, DeriveJoinMultiplicity(right).UnfilteredCols) != nil
md, left.Relational().NotNullCols, deriveUnfilteredCols(right)) != nil
}
leftColIDs := left.Relational().NotNullCols
rightColIDs := DeriveJoinMultiplicity(right).UnfilteredCols
rightColIDs := deriveUnfilteredCols(right)
if rightColIDs.Empty() {
// Right input has no unfiltered columns.
return false
......@@ -355,8 +383,8 @@ func makeForeignKeyMap(
leftCol := fkTableID.ColumnID(leftOrd)
rightCol := refTableID.ColumnID(rightOrd)
if !leftNotNullCols.Contains(leftCol) {
// Not all FK columns are part of the equality conditions. There are two
// cases:
// Not all FK columns are part of the equality conditions. There are
// two cases:
// 1. MATCH SIMPLE/PARTIAL: if this column is nullable, rows from this
// foreign key are not guaranteed to match.
// 2. MATCH FULL: FK rows are still guaranteed to match because the
......
This diff is collapsed.
......@@ -36,7 +36,8 @@ inner-join (lookup abcd)
│ ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
│ ├── scan small
│ │ ├── columns: m:1(int) n:2(int)
│ │ └── prune: (1,2)
│ │ ├── prune: (1,2)
│ │ └── unfiltered-cols: (1,2)
│ └── filters (true)
└── filters (true)
......@@ -85,7 +86,8 @@ inner-join (lookup abcd)
│ ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
│ ├── scan small
│ │ ├── columns: m:1(int) n:2(int)
│ │ └── prune: (1,2)
│ │ ├── prune: (1,2)
│ │ └── unfiltered-cols: (1,2)
│ └── filters
│ └── gt [type=bool, outer=(5), constraints=(/5: [/3 - ]; tight)]
│ ├── variable: b:5 [type=int]
......@@ -108,7 +110,8 @@ inner-join (lookup abcd)
│ ├── fd: (7)-->(4,5), (1)==(4), (4)==(1)
│ ├── scan small
│ │ ├── columns: m:1(int) n:2(int)
│ │ └── prune: (1,2)
│ │ ├── prune: (1,2)
│ │ └── unfiltered-cols: (1,2)
│ └── filters (true)
└── filters
└── gt [type=bool, outer=(6), constraints=(/6: [/3 - ]; tight)]
......@@ -130,7 +133,8 @@ inner-join (lookup abcd)
│ ├── fd: (7)-->(4,5), (1)==(4), (4)==(1), (2)==(5), (5)==(2)
│ ├── scan small
│ │ ├── columns: m:1(int) n:2(int)
│ │ └── prune: (1,2)
│ │ ├── prune: (1,2)
│ │ └── unfiltered-cols: (1,2)
│ └── filters (true)
└── filters
└── gt [type=bool, outer=(6), constraints=(/6: [/3 - ]; tight)]
......
......@@ -48,13 +48,15 @@ project
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2)
│ │ ├── prune: (1,2)
│ │ └── interesting orderings: (+1)
│ │ ├── interesting orderings: (+1)
│ │ └── unfiltered-cols: (1,2)
│ ├── scan uv
│ │ ├── columns: u:3(int) v:4(int!null) rowid:5(int!null)
│ │ ├── key: (5)
│ │ ├── fd: (5)-->(3,4)
│ │ ├── prune: (3-5)
│ │ └── interesting orderings: (+5)
│ │ ├── interesting orderings: (+5)
│ │ └── unfiltered-cols: (3-5)
│ └── filters (true)
└── projections
├── eq [as=a:6, type=bool, outer=(1,2), immutable]
......@@ -203,11 +205,11 @@ group-by
│ ├── interesting orderings: (+1)
│ └── inner-join (hash)
│ ├── columns: x:1(int!null) y:2(int) div:3(decimal) u:4(int!null) v:5(int!null)
│ ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
│ ├── stable, side-effects
│ ├── fd: (1)-->(2,3), (1)==(4), (4)==(1)
│ ├── prune: (2,3,5)
│ ├── interesting orderings: (+1)
│ ├── multiplicity: left-rows(zero-or-more), right-rows(one-or-zero)
│ ├── project
│ │ ├── columns: div:3(decimal) x:1(int!null) y:2(int)
│ │ ├── immutable, side-effects
......@@ -215,12 +217,14 @@ group-by
│ │ ├── fd: (1)-->(2,3)
│ │ ├── prune: (1-3)
│ │ ├── interesting orderings: (+1)
│ │ ├── unfiltered-cols: (1,2)
│ │ ├── scan xy
│ │ │ ├── columns: x:1(int!null) y:2(int)
│ │ │ ├── key: (1)
│ │ │ ├── fd: (1)-->(2)
│ │ │ ├── prune: (1,2)
│ │ │ └── interesting orderings: (+1)
│ │ │ ├── interesting orderings: (+1)
│ │ │ └── unfiltered-cols: (1,2)
│ │ └── projections
│ │ └── div [as=div:3, type=decimal, outer=(1,2), immutable, side-effects]
│ │ ├── variable: x:1 [type=int]
......
......@@ -47,13 +47,15 @@ select
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2)
│ │ ├── prune: (1,2)
│ │ └── interesting orderings: (+1)
│ │ ├── interesting orderings: (+1)
│ │ └── unfiltered-cols: (1,2)
│ ├── scan kuv
│ │ ├── columns: k:3(int!null) u:4(float) v:5(string)
│ │ ├── key: (3)
│ │ ├── fd: (3)-->(4,5)
│ │ ├── prune: (3-5)
│ │ └── interesting orderings: (+3)
│ │ ├── interesting orderings: (+3)
│ │ └── unfiltered-cols: (3-5)
│ └── filters (true)
└── filters
└── eq [type=bool, outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]
......
......@@ -45,7 +45,8 @@ project
│ │ │ ├── key: (3)
│ │ │ ├── fd: (3)-->(1,2)
│ │ │ ├── prune: (1-3)
│ │ │ └── interesting orderings: (+3)
│ │ │ ├── interesting orderings: (+3)
│ │ │ └── unfiltered-cols: (1-3)
│ │ ├── inner-join-apply
│ │ │ ├── columns: x:4(int!null) y:5(int) unnest:6(int)
│ │ │ ├── outer: (1,2)
......
This diff is collapsed.
......@@ -9,11 +9,11 @@ project
├── prune: (2,5)
└── left-join (cross)
├── columns: catalog_name:2(string!null) schema_name:3(string!null) default_character_set_name:4(string) sql_path:5(string) information_schema.tables.crdb_internal_vtable_pk:6(int) table_catalog:7(string) table_schema:8(string) table_name:9(string) table_type:10(string) is_insertable_into:11(string) version:12(int)
├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
├── fd: ()-->(3), (6)-->(7-12)
├── prune: (4-6,9-12)
├── reject-nulls: (6-12)
├── interesting orderings: (+6)
├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
├── project
│ ├── columns: catalog_name:2(string!null) schema_name:3(string!null) default_character_set_name:4(string) sql_path:5(string)
│ ├── fd: ()-->(3)
......@@ -39,7 +39,8 @@ project
│ ├── key: (6)
│ ├── fd: (6)-->(7-12)
│ ├── prune: (6-12)
│ └── interesting orderings: (+6)
│ ├── interesting orderings: (+6)
│ └── unfiltered-cols: (6-12)
└── filters
└── and [type=bool, outer=(2,3,7,8), constraints=(/2: (/NULL - ]; /3: (/NULL - ]; /7: (/NULL - ]; /8: (/NULL - ])]
├── eq [type=bool]
......
......@@ -379,7 +379,7 @@ memo (optimized, ~9KB, required=[presentation: field:6])
memo
SELECT DISTINCT tag FROM [SHOW TRACE FOR SESSION]
----
memo (optimized, ~7KB, required=[presentation: tag:11])
memo (optimized, ~6KB, required=[presentation: tag:11])
├── G1: (distinct-on G2 G3 cols=(11))
│ └── [presentation: tag:11]
│ ├── best: (distinct-on G2 G3 cols=(11))
......
......@@ -95,6 +95,7 @@ SELECT *, rowid FROM xysd INNER JOIN uv ON x=u
----
inner-join (hash)
├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int!null) v:6(int!null) rowid:7(int!null)
├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
├── stats: [rows=10000, distinct(1)=500, null(1)=0, distinct(2)=400, null(2)=0, distinct(5)=500, null(5)=0]
├── key: (7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6), (1)==(5), (5)==(1)
......@@ -116,6 +117,7 @@ SELECT *, rowid FROM xysd LEFT JOIN uv ON x=u
----
left-join (hash)
├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) v:6(int) rowid:7(int)
├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
├── stats: [rows=10000, distinct(5)=500, null(5)=0]
├── key: (1,7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -158,6 +160,7 @@ SELECT *, rowid FROM xysd FULL JOIN uv ON x=u
----
full-join (hash)
├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int) rowid:7(int)
├── multiplicity: left-rows(one-or-more), right-rows(exactly-one)
├── stats: [rows=10000]
├── key: (1,7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -295,6 +298,7 @@ group-by
├── fd: (1,6)-->(8)
├── inner-join (hash)
│ ├── columns: x:1(int!null) u:5(int!null) v:6(int!null)
│ ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
│ ├── stats: [rows=10000, distinct(1)=500, null(1)=0, distinct(5)=500, null(5)=0, distinct(1,6)=10000, null(1,6)=0]
│ ├── fd: (1)==(5), (5)==(1)
│ ├── scan xysd
......@@ -356,6 +360,7 @@ SELECT * FROM xysd JOIN uv ON x=u AND y=v
----
inner-join (hash)
├── columns: x:1(int!null) y:2(int!null) s:3(string) d:4(decimal!null) u:5(int!null) v:6(int!null)
├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
├── stats: [rows=25, distinct(1)=25, null(1)=0, distinct(2)=25, null(2)=0, distinct(5)=25, null(5)=0, distinct(6)=25, null(6)=0]
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(5), (5)==(1), (2)==(6), (6)==(2)
├── scan xysd
......@@ -376,6 +381,7 @@ SELECT * FROM xysd JOIN uv ON x=u AND y+v=5 AND y > 0 AND y < 300
----
inner-join (hash)
├── columns: x:1(int!null) y:2(int!null) s:3(string) d:4(decimal!null) u:5(int!null) v:6(int!null)
├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
├── stats: [rows=3333.33333, distinct(1)=500, null(1)=0, distinct(5)=500, null(5)=0]
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(5), (5)==(1)
├── select
......@@ -482,6 +488,7 @@ project
├── fd: (2)-->(8)
├── left-join (hash)
│ ├── columns: x:1(int!null) y:2(int) u:5(int) v:6(int)
│ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
│ ├── stats: [rows=5000, distinct(2)=400, null(2)=0, distinct(5)=500, null(5)=1666.66667]
│ ├── fd: (1)-->(2)
│ ├── scan xysd
......@@ -515,6 +522,7 @@ project
├── fd: (2)-->(8)
├── left-join (hash)
│ ├── columns: x:1(int) y:2(int) u:5(int) v:6(int!null)
│ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
│ ├── stats: [rows=10000, distinct(1)=500, null(1)=6666.66667, distinct(2)=399.903879, null(2)=6666.66667]
│ ├── fd: (1)-->(2)
│ ├── scan uv
......@@ -548,6 +556,7 @@ project
├── fd: (2)-->(8)
├── full-join (hash)
│ ├── columns: x:1(int) y:2(int) u:5(int) v:6(int)
│ ├── multiplicity: left-rows(one-or-more), right-rows(exactly-one)
│ ├── stats: [rows=11666.6667, distinct(2)=400, null(2)=6666.66667]
│ ├── fd: (1)-->(2)
│ ├── scan xysd
......@@ -676,6 +685,7 @@ SELECT *, rowid FROM xysd INNER JOIN uv ON x=u
----
inner-join (hash)
├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int!null) v:6(int!null) rowid:7(int!null)
├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
├── stats: [rows=5000, distinct(1)=499, null(1)=0, distinct(2)=399.99851, null(2)=2500, distinct(3)=499.977311, null(3)=50, distinct(5)=499, null(5)=0, distinct(2,3)=3160.69477, null(2,3)=25, distinct(3,5)=5000, null(3,5)=0, distinct(1,2,7)=5000, null(1,2,7)=0]
├── key: (7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6), (1)==(5), (5)==(1)
......@@ -697,6 +707,7 @@ SELECT *, rowid FROM xysd LEFT JOIN uv ON x=u
----
left-join (hash)
├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) u:5(int) v:6(int) rowid:7(int)
├── multiplicity: left-rows(one-or-more), right-rows(zero-or-one)
├── stats: [rows=10000, distinct(2)=400, null(2)=5000, distinct(3)=500, null(3)=100, distinct(5)=500, null(5)=0, distinct(2,3)=5000, null(2,3)=50, distinct(3,5)=10000, null(3,5)=50, distinct(1,2,7)=10000, null(1,2,7)=0]
├── key: (1,7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -739,6 +750,7 @@ SELECT *, rowid FROM xysd FULL JOIN uv ON x=u
----
full-join (hash)
├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int) rowid:7(int)
├── multiplicity: left-rows(one-or-more), right-rows(exactly-one)
├── stats: [rows=10000, distinct(2)=400, null(2)=5000, distinct(3)=500, null(3)=100, distinct(5)=500, null(5)=5000, distinct(2,3)=5000, null(2,3)=50, distinct(3,5)=10000, null(3,5)=50, distinct(1,2,7)=10000, null(1,2,7)=0]
├── key: (1,7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -766,6 +778,7 @@ select
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
├── full-join (hash)
│ ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int) rowid:7(int)
│ ├── multiplicity: left-rows(one-or-more), right-rows(exactly-one)
│ ├── stats: [rows=10000, distinct(2)=400, null(2)=5000, distinct(3)=500, null(3)=100, distinct(5)=500, null(5)=5000, distinct(2,3)=5000, null(2,3)=50, distinct(3,5)=10000, null(3,5)=50, distinct(1,2,7)=10000, null(1,2,7)=0]
│ ├── key: (1,7)
│ ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -791,6 +804,7 @@ SELECT *, rowid FROM xysd FULL JOIN uv ON u > 4 AND u < 2
----
full-join (cross)
├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) u:5(int) v:6(int) rowid:7(int)
├── multiplicity: left-rows(one-or-more), right-rows(one-or-more)
├── stats: [rows=50000000, distinct(2)=400, null(2)=25000000, distinct(3)=500, null(3)=500000, distinct(5)=500, null(5)=25000000, distinct(2,3)=5000, null(2,3)=250000, distinct(3,5)=250000, null(3,5)=250000, distinct(1,2,7)=50000000, null(1,2,7)=0]
├── key: (1,7)
├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
......@@ -966,6 +980,7 @@ SELECT * FROM (SELECT 1) LEFT JOIN (SELECT 1 WHERE false) ON true
left-join (cross)
├── columns: "?column?":1(int!null) "?column?":2(int)
├── cardinality: [1 - 1]
├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
├── stats: [rows=1]
├── key: ()
├── fd: ()-->(1,2)
......@@ -1000,6 +1015,7 @@ SELECT * FROM (SELECT 1) FULL JOIN (SELECT 1 WHERE false) ON true
left-join (cross)
├── columns: "?column?":1(int!null) "?column?":2(int)
├── cardinality: [1 - 1]
├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
├── stats: [rows=1]
├── key: ()
├── fd: ()-->(1,2)
......@@ -1044,6 +1060,7 @@ SELECT * FROM (SELECT 1 WHERE false) RIGHT JOIN (SELECT 1) ON true
left-join (cross)
├── columns: "?column?":1(int) "?column?":2(int!null)
├── cardinality: [1 - 1]
├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
├── stats: [rows=1]
├── key: ()
├── fd: ()-->(1,2)
......@@ -1068,6 +1085,7 @@ SELECT * FROM (SELECT 1 WHERE false) FULL JOIN (SELECT 1) ON true
left-join (cross)
├── columns: "?column?":1(int) "?column?":2(int!null)
├── cardinality: [1 - 1]
├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
├── stats: [rows=1]
├── key: ()
├── fd: ()-->(1,2)
......@@ -1092,6 +1110,7 @@ SELECT * FROM (SELECT 1) FULL JOIN (VALUES (1), (2)) ON true
inner-join (cross)
├── columns: "?column?":1(int!null) column1:2(int!null)
├── cardinality: [2 - 2]
├── multiplicity: left-rows(exactly-one), right-rows(one-or-more)
├── stats: [rows=2]
├── fd: ()-->(1)
├── values
......@@ -1160,6 +1179,7 @@ project
│ │ │ ├── fd: ()-->(15)
│ │ │ ├── left-join (cross)
│ │ │ │ ├── outer: (2)
│ │ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more)
│ │ │ │ ├── stats: [rows=1000, distinct(2)=1, null(2)=0]
│ │ │ │ ├── scan table1
│ │ │ │ │ └── stats: [rows=1000]
......@@ -1184,6 +1204,7 @@ SELECT * FROM (SELECT 1) AS a(x) LEFT JOIN (SELECT 2) AS b(x) ON a.x = b.x
left-join (cross)
├── columns: x:1(int!null) x:2(int)
├── cardinality: [1 - 1]
├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=1]
├── key: ()
├── fd: ()-->(1,2)
......@@ -1392,6 +1413,7 @@ FROM
----
full-join (cross)
├── columns: a:1(int) b:2(int) c:3(int) a:4(int) b:5(int) c:6(int)
├── multiplicity: left-rows(exactly-one), right-rows(one-or-more)
├── immutable
├── stats: [rows=100]
├── key: (1,2)
......
......@@ -1430,6 +1430,7 @@ with &1 (t)
│ ├── stats: [rows=4e+20]
│ ├── left-join (cross)
│ │ ├── columns: t1.x:1(bool) t2.x:3(bool)
│ │ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
│ │ ├── stats: [rows=4e+20]
│ │ ├── scan t1
│ │ │ ├── columns: t1.x:1(bool)
......
......@@ -123,6 +123,7 @@ with &1
│ │ ├── fd: ()-->(8,12), (5)~~>(4), (9)-->(10,11)
│ │ ├── left-join (hash)
│ │ │ ├── columns: a:4(int!null) b:5(string) column8:8(float) xyz.x:9(string) xyz.y:10(int) xyz.z:11(float)
│ │ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
│ │ │ ├── immutable
│ │ │ ├── stats: [rows=9.94974874, distinct(9)=9.94974874, null(9)=0]
│ │ │ ├── lax-key: (5,9)
......@@ -268,6 +269,7 @@ upsert uv
│ ├── fd: ()-->(10), (6)~~>(7), (8)-->(9), (9)~~>(8)
│ ├── left-join (hash)
│ │ ├── columns: z:6(int) column7:7(int) u:8(int) v:9(int)
│ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
│ │ ├── volatile, side-effects
│ │ ├── stats: [rows=1000, distinct(9)=991, null(9)=0]
│ │ ├── lax-key: (6,8)
......@@ -349,6 +351,7 @@ upsert mno
│ ├── fd: ()-->(10), (4)-->(5,6), (5,6)~~>(4), (7)-->(8,9), (8,9)~~>(7)
│ ├── left-join (hash)
│ │ ├── columns: m:4(int!null) n:5(int) o:6(int) m:7(int) n:8(int) o:9(int)
│ │ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
│ │ ├── stats: [rows=2000, distinct(8)=21.0526316, null(8)=1988.94737, distinct(9)=21.0526316, null(9)=2000]
│ │ ├── key: (4,7)
│ │ ├── fd: (4)-->(5,6), (5,6)~~>(4), (7)-->(8,9), (8,9)~~>(7)
......
......@@ -119,6 +119,7 @@ with &1 (t0)
│ ├── inner-join (cross)
│ │ ├── columns: true_agg:25(bool!null)
│ │ ├── cardinality: [0 - 2]
│ │ ├── multiplicity: left-rows(zero-or-one), right-rows(one-or-more)
│ │ ├── stats: [rows=2]
│ │ ├── fd: ()-->(25)
│ │ ├── with-scan &1 (t0)
......
......@@ -1252,6 +1252,7 @@ scalar-group-by
│ ├── full-join (hash)
│ │ ├── save-table-name: consistency_12_full_join_3
│ │ ├── columns: o_id:1(int) o_d_id:2(int) o_w_id:3(int) ol_o_id:9(int) ol_d_id:10(int) ol_w_id:11(int)
│ │ ├── multiplicity: left-rows(exactly-one), right-rows(one-or-more)
│ │ ├── stats: [rows=899134, distinct(1)=2999, null(1)=629303.857, distinct(2)=10, null(2)=629303.857, distinct(3)=10, null(3)=629303.857, distinct(9)=2999, null(9)=0, distinct(10)=10, null(10)=0, distinct(11)=10, null(11)=0]
│ │ ├── project
│ │ │ ├── save-table-name: consistency_12_project_4
......
......@@ -116,6 +116,7 @@ project
│ │ │ ├── inner-join (hash)
│ │ │ │ ├── save-table-name: q2_inner_join_7
│ │ │ │ ├── columns: ps_partkey:29(int!null) ps_suppkey:30(int!null) ps_supplycost:32(float!null) s_suppkey:34(int!null) s_nationkey:37(int!null) n_nationkey:41(int!null) n_regionkey:43(int!null) r_regionkey:45(int!null) r_name:46(char!null)
│ │ │ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)