github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/insubquery.go (about) 1 // Copyright 2020-2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plan 16 17 import ( 18 "fmt" 19 20 "github.com/dolthub/go-mysql-server/sql" 21 "github.com/dolthub/go-mysql-server/sql/expression" 22 "github.com/dolthub/go-mysql-server/sql/types" 23 ) 24 25 // InSubquery is an expression that checks an expression is in the result of a subquery. It's in the plan package, 26 // instead of the expression package, because Subquery is itself in the plan package (because it functions more like a 27 // plan node than an expression in its evaluation). 28 type InSubquery struct { 29 expression.BinaryExpressionStub 30 } 31 32 var _ sql.Expression = (*InSubquery)(nil) 33 var _ sql.CollationCoercible = (*InSubquery)(nil) 34 35 // Type implements sql.Expression 36 func (in *InSubquery) Type() sql.Type { 37 return types.Boolean 38 } 39 40 // CollationCoercibility implements the interface sql.CollationCoercible. 41 func (*InSubquery) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 42 return sql.Collation_binary, 5 43 } 44 45 // NewInSubquery creates an InSubquery expression. 46 func NewInSubquery(left sql.Expression, right sql.Expression) *InSubquery { 47 return &InSubquery{expression.BinaryExpressionStub{LeftChild: left, RightChild: right}} 48 } 49 50 var nilKey, _ = sql.HashOf(sql.NewRow(nil)) 51 52 // Eval implements the Expression interface. 53 func (in *InSubquery) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 54 typ := in.LeftChild.Type().Promote() 55 left, err := in.LeftChild.Eval(ctx, row) 56 if err != nil { 57 return nil, err 58 } 59 60 // The NULL handling for IN expressions is tricky. According to 61 // https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in: 62 // To comply with the SQL standard, IN() returns NULL not only if the expression on the left hand side is NULL, but 63 // also if no match is found in the list and one of the expressions in the list is NULL. 64 // However, there's a strange edge case. NULL IN (empty list) return 0, not NULL. 65 leftNull := left == nil 66 67 left, _, err = typ.Convert(left) 68 if err != nil { 69 return nil, err 70 } 71 72 switch right := in.RightChild.(type) { 73 case *Subquery: 74 if types.NumColumns(typ) != types.NumColumns(right.Type()) { 75 return nil, sql.ErrInvalidOperandColumns.New(types.NumColumns(typ), types.NumColumns(right.Type())) 76 } 77 78 typ := right.Type() 79 80 values, err := right.HashMultiple(ctx, row) 81 if err != nil { 82 return nil, err 83 } 84 85 // NULL IN (list) returns NULL. NULL IN (empty list) returns 0 86 if leftNull { 87 if values.Size() == 0 { 88 return false, nil 89 } 90 return nil, nil 91 } 92 93 // convert left to right's type 94 nLeft, _, err := typ.Convert(left) 95 if err != nil { 96 return false, nil 97 } 98 99 key, err := sql.HashOf(sql.NewRow(nLeft)) 100 if err != nil { 101 return nil, err 102 } 103 104 val, notFoundErr := values.Get(key) 105 if notFoundErr != nil { 106 if _, nilValNotFoundErr := values.Get(nilKey); nilValNotFoundErr == nil { 107 return nil, nil 108 } 109 return false, nil 110 } 111 112 val, _, err = typ.Convert(val) 113 if err != nil { 114 return false, nil 115 } 116 117 cmp, err := typ.Compare(left, val) 118 if err != nil { 119 return nil, err 120 } 121 122 return cmp == 0, nil 123 124 default: 125 return nil, expression.ErrUnsupportedInOperand.New(right) 126 } 127 } 128 129 // WithChildren implements the Expression interface. 130 func (in *InSubquery) WithChildren(children ...sql.Expression) (sql.Expression, error) { 131 if len(children) != 2 { 132 return nil, sql.ErrInvalidChildrenNumber.New(in, len(children), 2) 133 } 134 return NewInSubquery(children[0], children[1]), nil 135 } 136 137 // Describe implements the sql.Describable interface 138 func (in *InSubquery) Describe(options sql.DescribeOptions) string { 139 pr := sql.NewTreePrinter() 140 _ = pr.WriteNode("InSubquery") 141 children := []string{fmt.Sprintf("left: %s", sql.Describe(in.Left(), options)), 142 fmt.Sprintf("right: %s", sql.Describe(in.Right(), options))} 143 _ = pr.WriteChildren(children...) 144 return pr.String() 145 } 146 147 // String implements the fmt.Stringer interface 148 func (in *InSubquery) String() string { 149 return in.Describe(sql.DescribeOptions{ 150 Analyze: false, 151 Estimates: false, 152 Debug: false, 153 }) 154 } 155 156 // DebugString implements the sql.DebugStringer interface 157 func (in *InSubquery) DebugString() string { 158 return in.Describe(sql.DescribeOptions{ 159 Analyze: false, 160 Estimates: false, 161 Debug: true, 162 }) 163 } 164 165 // Children implements the Expression interface. 166 func (in *InSubquery) Children() []sql.Expression { 167 return []sql.Expression{in.LeftChild, in.RightChild} 168 } 169 170 // Dispose implements sql.Disposable 171 func (in *InSubquery) Dispose() { 172 if sq, ok := in.RightChild.(*Subquery); ok { 173 sq.Dispose() 174 } 175 } 176 177 // NewNotInSubquery creates a new NotInSubquery expression. 178 func NewNotInSubquery(left sql.Expression, right sql.Expression) sql.Expression { 179 return expression.NewNot(NewInSubquery(left, right)) 180 }