github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }