Commit b9242838 authored by craig[bot]'s avatar craig[bot]

Merge #34531 #34592

34531: ui: implement logical plan design r=celiala a=celiala

This commit implements the designs as specified in
Zeplin design "Logical Plans - v1":
https://app.zeplin.io/project/5c40e9dab2616abf79e3eb99/screen/5c4b6f23143de2bfcb12ae4e

Note that remaining design items will be addressed in future commits (tracked in #34545).

![logical-plans](https://user-images.githubusercontent.com/3051672/52312291-11070d80-2978-11e9-8ecc-42ab4a859371.gif)

Release note: None

34592: opt: Fix panic in SQLSmith query r=andy-kimball a=andy-kimball

A SQLSmith query exposed a bug in the expression interner. This bug caused
the interner to consider empty literal arrays to be equal, even if their
static types are different. For example:

  ARRAY[]:::string[]
  ARRAY[]:::int[]

An empty string array should not be treated as the same as an empty int
array. The fix is to consult the static type of the array in addition to the
types of its elements (but only when there are zero elements or one of the
elements is null). In addition, I took the opportunity to make the existing
Tuple interner code faster, by only comparing static types when lables or
nulls are present.

Fixes #34439

Release note: None
Co-authored-by: default avatarCelia La <[email protected]>
Co-authored-by: default avatarAndrew Kimball <[email protected]>
...@@ -363,16 +363,32 @@ func (h *hasher) HashDatum(val tree.Datum) { ...@@ -363,16 +363,32 @@ func (h *hasher) HashDatum(val tree.Datum) {
case *tree.DJSON: case *tree.DJSON:
h.HashString(t.String()) h.HashString(t.String())
case *tree.DTuple: case *tree.DTuple:
for _, d := range t.D { // If labels are present, then hash of tuple's static type is needed to
h.HashDatum(d) // disambiguate when everything is the same except labels.
} alwaysHashType := len(t.ResolvedType().(types.TTuple).Labels) != 0
h.HashDatumType(t.ResolvedType()) h.hashDatumsWithType(t.D, t.ResolvedType(), alwaysHashType)
case *tree.DArray: case *tree.DArray:
for _, d := range t.Array { // If the array is empty, then hash of tuple's static type is needed to
h.HashDatum(d) // disambiguate.
} alwaysHashType := len(t.Array) == 0
h.hashDatumsWithType(t.Array, t.ResolvedType(), alwaysHashType)
default: default:
h.HashBytes(encodeDatum(h.bytes[:0], val)) h.bytes = encodeDatum(h.bytes[:0], val)
h.HashBytes(h.bytes)
}
}
func (h *hasher) hashDatumsWithType(datums tree.Datums, typ types.T, alwaysHashType bool) {
for _, d := range datums {
if d == tree.DNull {
// At least one NULL exists, so need to compare static types (e.g. a
// NULL::int is indistinguishable from NULL::string).
alwaysHashType = true
}
h.HashDatum(d)
}
if alwaysHashType {
h.HashDatumType(typ)
} }
} }
...@@ -381,9 +397,10 @@ func (h *hasher) HashDatumType(val types.T) { ...@@ -381,9 +397,10 @@ func (h *hasher) HashDatumType(val types.T) {
} }
func (h *hasher) HashColType(val coltypes.T) { func (h *hasher) HashColType(val coltypes.T) {
buf := bytes.NewBuffer(h.bytes) buf := bytes.NewBuffer(h.bytes[:0])
val.Format(buf, lex.EncNoFlags) val.Format(buf, lex.EncNoFlags)
h.HashBytes(buf.Bytes()) h.bytes = buf.Bytes()
h.HashBytes(h.bytes)
} }
func (h *hasher) HashTypedExpr(val tree.TypedExpr) { func (h *hasher) HashTypedExpr(val tree.TypedExpr) {
...@@ -579,11 +596,13 @@ func (h *hasher) IsDatumTypeEqual(l, r types.T) bool { ...@@ -579,11 +596,13 @@ func (h *hasher) IsDatumTypeEqual(l, r types.T) bool {
} }
func (h *hasher) IsColTypeEqual(l, r coltypes.T) bool { func (h *hasher) IsColTypeEqual(l, r coltypes.T) bool {
lbuf := bytes.NewBuffer(h.bytes) lbuf := bytes.NewBuffer(h.bytes[:0])
l.Format(lbuf, lex.EncNoFlags) l.Format(lbuf, lex.EncNoFlags)
rbuf := bytes.NewBuffer(h.bytes2) rbuf := bytes.NewBuffer(h.bytes2[:0])
r.Format(rbuf, lex.EncNoFlags) r.Format(rbuf, lex.EncNoFlags)
return bytes.Equal(lbuf.Bytes(), rbuf.Bytes()) h.bytes = lbuf.Bytes()
h.bytes2 = rbuf.Bytes()
return bytes.Equal(h.bytes, h.bytes2)
} }
func (h *hasher) IsDatumEqual(l, r tree.Datum) bool { func (h *hasher) IsDatumEqual(l, r tree.Datum) bool {
...@@ -622,37 +641,56 @@ func (h *hasher) IsDatumEqual(l, r tree.Datum) bool { ...@@ -622,37 +641,56 @@ func (h *hasher) IsDatumEqual(l, r tree.Datum) bool {
} }
case *tree.DTuple: case *tree.DTuple:
if rt, ok := r.(*tree.DTuple); ok { if rt, ok := r.(*tree.DTuple); ok {
if len(lt.D) != len(rt.D) { // Compare datums and then compare static types if nulls or labels
// are present.
ltyp := lt.ResolvedType().(types.TTuple)
rtyp := rt.ResolvedType().(types.TTuple)
if !h.areDatumsWithTypeEqual(lt.D, rt.D, ltyp, rtyp) {
return false return false
} }
for i := range lt.D { return len(ltyp.Labels) == 0 || h.IsDatumTypeEqual(ltyp, rtyp)
if !h.IsDatumEqual(lt.D[i], rt.D[i]) {
return false
}
}
return h.IsDatumTypeEqual(l.ResolvedType(), r.ResolvedType())
} }
case *tree.DArray: case *tree.DArray:
if rt, ok := r.(*tree.DArray); ok { if rt, ok := r.(*tree.DArray); ok {
if len(lt.Array) != len(rt.Array) { // Compare datums and then compare static types if nulls are present
// or if arrays are empty.
ltyp := lt.ResolvedType()
rtyp := rt.ResolvedType()
if !h.areDatumsWithTypeEqual(lt.Array, rt.Array, ltyp, rtyp) {
return false return false
} }
for i := range lt.Array { return len(lt.Array) != 0 || h.IsDatumTypeEqual(ltyp, rtyp)
if !h.IsDatumEqual(lt.Array[i], rt.Array[i]) {
return false
}
}
return true
} }
default: default:
lb := encodeDatum(h.bytes[:0], l) h.bytes = encodeDatum(h.bytes[:0], l)
rb := encodeDatum(h.bytes2[:0], r) h.bytes2 = encodeDatum(h.bytes2[:0], r)
return bytes.Equal(lb, rb) return bytes.Equal(h.bytes, h.bytes2)
} }
return false return false
} }
func (h *hasher) areDatumsWithTypeEqual(ldatums, rdatums tree.Datums, ltyp, rtyp types.T) bool {
if len(ldatums) != len(rdatums) {
return false
}
foundNull := false
for i := range ldatums {
if !h.IsDatumEqual(ldatums[i], rdatums[i]) {
return false
}
if ldatums[i] == tree.DNull {
// At least one NULL exists, so need to compare static types (e.g. a
// NULL::int is indistinguishable from NULL::string).
foundNull = true
}
}
if foundNull {
return h.IsDatumTypeEqual(ltyp, rtyp)
}
return true
}
func (h *hasher) IsTypedExprEqual(l, r tree.TypedExpr) bool { func (h *hasher) IsTypedExprEqual(l, r tree.TypedExpr) bool {
return l == r return l == r
} }
......
...@@ -42,11 +42,16 @@ func TestInterner(t *testing.T) { ...@@ -42,11 +42,16 @@ func TestInterner(t *testing.T) {
tupTyp2 := types.TTuple{Types: []types.T{types.Int, types.String}, Labels: []string{"a", "b"}} tupTyp2 := types.TTuple{Types: []types.T{types.Int, types.String}, Labels: []string{"a", "b"}}
tupTyp3 := types.TTuple{Types: []types.T{types.Int, types.String}} tupTyp3 := types.TTuple{Types: []types.T{types.Int, types.String}}
tupTyp4 := types.TTuple{Types: []types.T{types.Int, types.String, types.Bool}} tupTyp4 := types.TTuple{Types: []types.T{types.Int, types.String, types.Bool}}
tupTyp5 := types.TTuple{Types: []types.T{types.Int, types.String}, Labels: []string{"c", "d"}}
tupTyp6 := types.TTuple{Types: []types.T{types.String, types.Int}, Labels: []string{"c", "d"}}
tup1 := tree.NewDTuple(tupTyp1, tree.NewDInt(100), tree.NewDString("foo")) tup1 := tree.NewDTuple(tupTyp1, tree.NewDInt(100), tree.NewDString("foo"))
tup2 := tree.NewDTuple(tupTyp2, tree.NewDInt(100), tree.NewDString("foo")) tup2 := tree.NewDTuple(tupTyp2, tree.NewDInt(100), tree.NewDString("foo"))
tup3 := tree.NewDTuple(tupTyp3, tree.NewDInt(100), tree.NewDString("foo")) tup3 := tree.NewDTuple(tupTyp3, tree.NewDInt(100), tree.NewDString("foo"))
tup4 := tree.NewDTuple(tupTyp4, tree.NewDInt(100), tree.NewDString("foo"), tree.DBoolTrue) tup4 := tree.NewDTuple(tupTyp4, tree.NewDInt(100), tree.NewDString("foo"), tree.DBoolTrue)
tup5 := tree.NewDTuple(tupTyp5, tree.NewDInt(100), tree.NewDString("foo"))
tup6 := tree.NewDTuple(tupTyp5, tree.DNull, tree.DNull)
tup7 := tree.NewDTuple(tupTyp6, tree.DNull, tree.DNull)
arr1 := tree.NewDArray(tupTyp1) arr1 := tree.NewDArray(tupTyp1)
arr1.Array = tree.Datums{tup1, tup2} arr1.Array = tree.Datums{tup1, tup2}
...@@ -54,6 +59,14 @@ func TestInterner(t *testing.T) { ...@@ -54,6 +59,14 @@ func TestInterner(t *testing.T) {
arr2.Array = tree.Datums{tup2, tup1} arr2.Array = tree.Datums{tup2, tup1}
arr3 := tree.NewDArray(tupTyp3) arr3 := tree.NewDArray(tupTyp3)
arr3.Array = tree.Datums{tup2, tup3} arr3.Array = tree.Datums{tup2, tup3}
arr4 := tree.NewDArray(types.Int)
arr4.Array = tree.Datums{tree.DNull}
arr5 := tree.NewDArray(types.String)
arr5.Array = tree.Datums{tree.DNull}
arr6 := tree.NewDArray(types.Int)
arr6.Array = tree.Datums{}
arr7 := tree.NewDArray(types.String)
arr7.Array = tree.Datums{}
dec1, _ := tree.ParseDDecimal("1.0") dec1, _ := tree.ParseDDecimal("1.0")
dec2, _ := tree.ParseDDecimal("1.0") dec2, _ := tree.ParseDDecimal("1.0")
...@@ -187,9 +200,14 @@ func TestInterner(t *testing.T) { ...@@ -187,9 +200,14 @@ func TestInterner(t *testing.T) {
{val1: tup1, val2: tup2, equal: true}, {val1: tup1, val2: tup2, equal: true},
{val1: tup2, val2: tup3, equal: false}, {val1: tup2, val2: tup3, equal: false},
{val1: tup3, val2: tup4, equal: false}, {val1: tup3, val2: tup4, equal: false},
{val1: tup1, val2: tup5, equal: false},
{val1: tup6, val2: tup7, equal: false},
{val1: arr1, val2: arr2, equal: true}, {val1: arr1, val2: arr2, equal: true},
{val1: arr2, val2: arr3, equal: false}, {val1: arr2, val2: arr3, equal: false},
{val1: arr4, val2: arr5, equal: false},
{val1: arr4, val2: arr6, equal: false},
{val1: arr6, val2: arr7, equal: false},
{val1: dec1, val2: dec2, equal: true}, {val1: dec1, val2: dec2, equal: true},
{val1: dec2, val2: dec3, equal: false}, {val1: dec2, val2: dec3, equal: false},
......
This diff is collapsed.
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
padding 12px 24px padding 12px 24px
color $body-color color $body-color
.last-cleared-tooltip, .numeric-stats-table .last-cleared-tooltip, .numeric-stats-table, .plan-view-table
&__tooltip &__tooltip
width 36px // Reserve space for 10px padding around centered 16px icon width 36px // Reserve space for 10px padding around centered 16px icon
height 16px height 16px
...@@ -169,25 +169,140 @@ ...@@ -169,25 +169,140 @@
top 8px top 8px
background-color $warning-color background-color $warning-color
.plan-node $plan-node-line-color = #DADADA // connecting line: light gray
font-family monospace $plan-node-warning-color = #D18737 // dark orange
$plan-node-warning-background-color = rgba(209, 135, 55, 0.06) // light orange
$plan-node-warning-border-color = rgba(209, 135, 55, 0.3) // dark orange 2
$plan-node-details-background-color = #F6F6F6 // grayish-white
$plan-node-details-border-color = #ACB8CB // dark gray
ul .plan-view-table
padding-left 20px @extend $table-base
&__row
&__name &--body
font-weight bold border-top $table-cell-border
&:nth-child(odd)
background-color $stats-table-tr--bg
&__attr-key &__tooltip
color orange width 520px
margin-left 5px
&__attr-value .hover-tooltip__text
color green width 520px
.plan-view .plan-view
margin-top 10px color $body-color
padding-left 30px position relative
background-color white
padding-top 5px .arrow-icon
padding-bottom 5px margin-left 8px
polyline
fill none
stroke $body-color
stroke-width 2
.node-icon
margin 0 10px 0 12px
.warning-icon
margin 0 6px 0 8px
position relative
top 3px
path
fill $plan-node-warning-color
.warn
position relative
color $plan-node-warning-color
background-color $plan-node-warning-background-color
.arrow-icon
polyline
fill none
stroke $plan-node-warning-color
stroke-width 2
.arrow
font-size smaller
display none
.arrow-expanded
font-size smaller
display inline
.nodeDetails
position relative
padding 6px 0
border 1px solid transparent
border-radius 3px
&.expanded
border 1px solid $plan-node-details-border-color
&.expanded
&:hover
background-color $plan-node-details-background-color
&:hover
cursor pointer
.arrow
display inline
&.warn
&.expanded
&:hover
position relative
background-color $plan-node-warning-background-color
border 1px solid $plan-node-warning-border-color
.nodeAttributes
color $body-color
padding 14px 16px
margin 6px 0 4px 0
border 1px solid $plan-node-line-color
background-color white
border-radius 2px
ul
padding 0
margin 0
li
padding 0
margin 0
position relative
list-style-type none
// vertical line, to previous node (above)
&:not(:first-child):after
content ''
width 1px
height 19px
background-color $plan-node-line-color
position absolute
top -10px
left 17px
ul
padding-left 40px
li
// first node: horizontal line, to parent
&:first-child:after
content ''
height 1px
width 34px
background-color $plan-node-line-color
position absolute
top 17px
left -22px
// vertical line, to parent
&:before
content ''
width 1px
height 100%
background-color $plan-node-line-color
position absolute
top -10px
left -23px
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment