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  }