github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/hash_lookup.go (about)

     1  // Copyright 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  	"sync"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql/types"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  )
    25  
    26  // NewHashLookup returns a node that performs an indexed hash lookup
    27  // of cached rows for fulfilling RowIter() calls. In particular, this
    28  // node sits directly on top of a `CachedResults` node and has two
    29  // expressions: a projection for hashing the Child row results and
    30  // another projection for hashing the parent row values when
    31  // performing a lookup. When RowIter is called, if cached results are
    32  // available, it fulfills the RowIter call by performing a hash lookup
    33  // on the projected results. If cached results are not available, it
    34  // simply delegates to the child.
    35  func NewHashLookup(n sql.Node, rightEntryKey sql.Expression, leftProbeKey sql.Expression, joinType JoinType) *HashLookup {
    36  	return &HashLookup{
    37  		UnaryNode:     UnaryNode{n},
    38  		RightEntryKey: rightEntryKey,
    39  		LeftProbeKey:  leftProbeKey,
    40  		Mutex:         new(sync.Mutex),
    41  		JoinType:      joinType,
    42  	}
    43  }
    44  
    45  type HashLookup struct {
    46  	UnaryNode
    47  	RightEntryKey sql.Expression
    48  	LeftProbeKey  sql.Expression
    49  	Mutex         *sync.Mutex
    50  	Lookup        *map[interface{}][]sql.Row
    51  	JoinType      JoinType
    52  }
    53  
    54  var _ sql.Node = (*HashLookup)(nil)
    55  var _ sql.Expressioner = (*HashLookup)(nil)
    56  var _ sql.CollationCoercible = (*HashLookup)(nil)
    57  
    58  func (n *HashLookup) Expressions() []sql.Expression {
    59  	return []sql.Expression{n.RightEntryKey, n.LeftProbeKey}
    60  }
    61  
    62  func (n *HashLookup) IsReadOnly() bool {
    63  	return n.Child.IsReadOnly()
    64  }
    65  
    66  func (n *HashLookup) WithExpressions(exprs ...sql.Expression) (sql.Node, error) {
    67  	if len(exprs) != 2 {
    68  		return nil, sql.ErrInvalidChildrenNumber.New(n, len(exprs), 2)
    69  	}
    70  	ret := *n
    71  	ret.RightEntryKey = exprs[0]
    72  	ret.LeftProbeKey = exprs[1]
    73  	return &ret, nil
    74  }
    75  
    76  func (n *HashLookup) String() string {
    77  	pr := sql.NewTreePrinter()
    78  	_ = pr.WriteNode("HashLookup")
    79  	children := make([]string, 3)
    80  	children[0] = fmt.Sprintf("left-key: %s", n.LeftProbeKey)
    81  	children[1] = fmt.Sprintf("right-key: %s", n.RightEntryKey)
    82  	children[2] = n.Child.String()
    83  	_ = pr.WriteChildren(children...)
    84  	return pr.String()
    85  }
    86  
    87  func (n *HashLookup) DebugString() string {
    88  	pr := sql.NewTreePrinter()
    89  	_ = pr.WriteNode("HashLookup")
    90  	children := make([]string, 3)
    91  	children[0] = fmt.Sprintf("left-key: %s", sql.DebugString(n.LeftProbeKey))
    92  	children[1] = fmt.Sprintf("right-key: %s", sql.DebugString(n.RightEntryKey))
    93  	children[2] = sql.DebugString(n.Child)
    94  	_ = pr.WriteChildren(children...)
    95  	return pr.String()
    96  }
    97  
    98  func (n *HashLookup) WithChildren(children ...sql.Node) (sql.Node, error) {
    99  	if len(children) != 1 {
   100  		return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 1)
   101  	}
   102  	nn := *n
   103  	nn.UnaryNode.Child = children[0]
   104  	return &nn, nil
   105  }
   106  
   107  // CheckPrivileges implements the interface sql.Node.
   108  func (n *HashLookup) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   109  	return n.Child.CheckPrivileges(ctx, opChecker)
   110  }
   111  
   112  // CollationCoercibility implements the interface sql.CollationCoercible.
   113  func (n *HashLookup) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   114  	return sql.GetCoercibility(ctx, n.Child)
   115  }
   116  
   117  // Convert a tuple expression returning []interface{} into something comparable.
   118  // Fast paths a few smaller slices into fixed size arrays, puts everything else
   119  // through string serialization and a hash for now. It is OK to hash lossy here
   120  // as the join condition is still evaluated after the matching rows are returned.
   121  func (n *HashLookup) GetHashKey(ctx *sql.Context, e sql.Expression, row sql.Row) (interface{}, error) {
   122  	key, err := e.Eval(ctx, row)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	key, _, err = n.LeftProbeKey.Type().Convert(key)
   127  	if types.ErrValueNotNil.Is(err) {
   128  		// The LHS expression was NullType. This is allowed.
   129  		return nil, nil
   130  	}
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	if s, ok := key.([]interface{}); ok {
   135  		return sql.HashOf(s)
   136  	}
   137  	// byte slices are not hashable
   138  	if k, ok := key.([]byte); ok {
   139  		key = string(k)
   140  	}
   141  	return key, nil
   142  }
   143  
   144  func (n *HashLookup) Dispose() {
   145  	n.Lookup = nil
   146  }