github.com/m3db/m3@v1.5.0/src/query/functions/binary/base.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package binary 22 23 import ( 24 "fmt" 25 "sync" 26 27 "github.com/m3db/m3/src/query/block" 28 "github.com/m3db/m3/src/query/executor/transform" 29 "github.com/m3db/m3/src/query/models" 30 "github.com/m3db/m3/src/query/parser" 31 "github.com/m3db/m3/src/x/opentracing" 32 ) 33 34 type baseOp struct { 35 OperatorType string 36 processFunc processFunc 37 params NodeParams 38 } 39 40 // OpType for the operator. 41 func (o baseOp) OpType() string { 42 return o.OperatorType 43 } 44 45 func (o baseOp) String() string { 46 return fmt.Sprintf("type: %s, lnode: %s, rnode: %s", o.OpType(), 47 o.params.LNode, o.params.RNode) 48 } 49 50 // Node creates an execution node. 51 func (o baseOp) Node( 52 controller *transform.Controller, 53 _ transform.Options, 54 ) transform.OpNode { 55 return &baseNode{ 56 op: o, 57 process: o.processFunc, 58 controller: controller, 59 cache: transform.NewBlockCache(), 60 } 61 } 62 63 // ArithmeticFunction returns the arithmetic function for this operation type. 64 func ArithmeticFunction(opType string, returnBool bool) (binaryFunction, error) { 65 if fn, ok := arithmeticFuncs[opType]; ok { 66 return fn, nil 67 } 68 69 // For comparison functions, check if returning bool or not and return the 70 // appropriate one. 71 if returnBool { 72 opType += returnBoolSuffix 73 } 74 75 if fn, ok := comparisonFuncs[opType]; ok { 76 return fn, nil 77 } 78 79 return nil, fmt.Errorf("no arithmetic function found for type: %s", opType) 80 } 81 82 // NewOp creates a new binary operation. 83 func NewOp( 84 opType string, 85 params NodeParams, 86 ) (parser.Params, error) { 87 if params.VectorMatcherBuilder == nil { 88 params.VectorMatcherBuilder = defaultVectorMatcherBuilder 89 } 90 91 fn, ok := buildLogicalFunction(opType, params) 92 if !ok { 93 fn, ok = buildArithmeticFunction(opType, params) 94 if !ok { 95 fn, ok = buildComparisonFunction(opType, params) 96 if !ok { 97 return baseOp{}, fmt.Errorf("operator not supported: %s", opType) 98 } 99 } 100 } 101 102 return baseOp{ 103 OperatorType: opType, 104 processFunc: fn, 105 params: params, 106 }, nil 107 } 108 109 type baseNode struct { 110 op baseOp 111 process processFunc 112 controller *transform.Controller 113 cache *transform.BlockCache 114 mu sync.Mutex 115 } 116 117 func (n *baseNode) Params() parser.Params { 118 return n.op 119 } 120 121 type processFunc func(*models.QueryContext, block.Block, 122 block.Block, *transform.Controller) (block.Block, error) 123 124 // Process processes a block. 125 func (n *baseNode) Process(queryCtx *models.QueryContext, 126 ID parser.NodeID, b block.Block) error { 127 lhs, rhs, err := n.computeOrCache(ID, b) 128 if err != nil { 129 // Clean up any blocks from cache 130 n.cleanup() 131 return err 132 } 133 134 // Both blocks are not ready 135 if lhs == nil || rhs == nil { 136 return nil 137 } 138 139 n.cleanup() 140 141 nextBlock, err := n.processWithTracing(queryCtx, lhs, rhs) 142 if err != nil { 143 return err 144 } 145 146 defer nextBlock.Close() 147 return n.controller.Process(queryCtx, nextBlock) 148 } 149 150 func (n *baseNode) processWithTracing(queryCtx *models.QueryContext, 151 lhs block.Block, rhs block.Block) (block.Block, error) { 152 sp, ctx := opentracing.StartSpanFromContext(queryCtx.Ctx, n.op.OpType()) 153 defer sp.Finish() 154 queryCtx = queryCtx.WithContext(ctx) 155 156 return n.process(queryCtx, lhs, rhs, n.controller) 157 } 158 159 // computeOrCache figures out if both lhs and rhs are available, 160 // if not then it caches the incoming block. 161 func (n *baseNode) computeOrCache( 162 ID parser.NodeID, 163 b block.Block, 164 ) (block.Block, block.Block, error) { 165 var lhs, rhs block.Block 166 n.mu.Lock() 167 defer n.mu.Unlock() 168 op := n.op 169 params := op.params 170 if params.LNode == ID { 171 rBlock, ok := n.cache.Get(params.RNode) 172 if !ok { 173 return lhs, rhs, n.cache.Add(ID, b) 174 } 175 176 rhs = rBlock 177 lhs = b 178 } else if params.RNode == ID { 179 lBlock, ok := n.cache.Get(params.LNode) 180 if !ok { 181 return lhs, rhs, n.cache.Add(ID, b) 182 } 183 184 lhs = lBlock 185 rhs = b 186 } 187 188 return lhs, rhs, nil 189 } 190 191 func (n *baseNode) cleanup() { 192 n.mu.Lock() 193 defer n.mu.Unlock() 194 params := n.op.params 195 n.cache.Remove(params.LNode) 196 n.cache.Remove(params.RNode) 197 }