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 }