github.com/m3db/m3@v1.5.0/src/query/functions/binary/binary.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  	"github.com/m3db/m3/src/query/block"
    25  	"github.com/m3db/m3/src/query/executor/transform"
    26  	"github.com/m3db/m3/src/query/functions/utils"
    27  	"github.com/m3db/m3/src/query/models"
    28  )
    29  
    30  type binaryFunction func(x, y float64) float64
    31  type singleScalarFunc func(x float64) float64
    32  
    33  // processes two logical blocks, performing a logical operation on them.
    34  func processBinary(
    35  	queryCtx *models.QueryContext,
    36  	lhs, rhs block.Block,
    37  	params NodeParams,
    38  	controller *transform.Controller,
    39  	isComparison bool,
    40  	fn binaryFunction,
    41  ) (block.Block, error) {
    42  	lIter, err := lhs.StepIter()
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if lhs.Info().Type() == block.BlockScalar {
    48  		scalarL, ok := lhs.(*block.Scalar)
    49  		if !ok {
    50  			return nil, errLeftScalar
    51  		}
    52  
    53  		lVal := scalarL.Value()
    54  		// rhs is a series; use rhs metadata and series meta
    55  		if rhs.Info().Type() != block.BlockScalar {
    56  			return processSingleBlock(
    57  				queryCtx,
    58  				rhs,
    59  				controller,
    60  				func(x float64) float64 {
    61  					return fn(lVal, x)
    62  				},
    63  			)
    64  		}
    65  
    66  		// if both lhs and rhs are scalars, can create a new block
    67  		// by extracting values from lhs and rhs instead of doing
    68  		// by-value comparisons.
    69  		scalarR, ok := rhs.(*block.Scalar)
    70  		if !ok {
    71  			return nil, errRightScalar
    72  		}
    73  
    74  		// NB(arnikola): this is a sanity check, as scalar comparisons
    75  		// should have previously errored out during the parsing step.
    76  		if !params.ReturnBool && isComparison {
    77  			return nil, errNoModifierForComparison
    78  		}
    79  
    80  		return block.NewScalar(
    81  			fn(lVal, scalarR.Value()),
    82  			lhs.Meta(),
    83  		), nil
    84  	}
    85  
    86  	if rhs.Info().Type() == block.BlockScalar {
    87  		scalarR, ok := rhs.(*block.Scalar)
    88  		if !ok {
    89  			return nil, errRightScalar
    90  		}
    91  
    92  		rVal := scalarR.Value()
    93  		// lhs is a series; use lhs metadata and series meta.
    94  		return processSingleBlock(
    95  			queryCtx,
    96  			lhs,
    97  			controller,
    98  			func(x float64) float64 {
    99  				return fn(x, rVal)
   100  			},
   101  		)
   102  	}
   103  
   104  	// both lhs and rhs are series.
   105  	rIter, err := rhs.StepIter()
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	matcher := params.VectorMatcherBuilder(lhs, rhs)
   111  	// NB(arnikola): this is a sanity check, as functions between
   112  	// two series missing vector matching should have previously
   113  	// errored out during the parsing step.
   114  	if !matcher.Set {
   115  		return nil, errNoMatching
   116  	}
   117  
   118  	return processBothSeries(queryCtx, lhs.Meta(), rhs.Meta(), lIter, rIter,
   119  		controller, matcher, fn)
   120  }
   121  
   122  func processSingleBlock(
   123  	queryCtx *models.QueryContext,
   124  	block block.Block,
   125  	controller *transform.Controller,
   126  	fn singleScalarFunc,
   127  ) (block.Block, error) {
   128  	it, err := block.StepIter()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	meta := block.Meta()
   134  	metas := it.SeriesMeta()
   135  	meta, metas = removeNameTags(meta, metas)
   136  	builder, err := controller.BlockBuilder(queryCtx, meta, metas)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	if err = builder.AddCols(it.StepCount()); err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	for index := 0; it.Next(); index++ {
   146  		step := it.Current()
   147  		values := step.Values()
   148  		for _, value := range values {
   149  			if err := builder.AppendValue(index, fn(value)); err != nil {
   150  				return nil, err
   151  			}
   152  		}
   153  	}
   154  
   155  	if err = it.Err(); err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	return builder.Build(), nil
   160  }
   161  
   162  func processBothSeries(
   163  	queryCtx *models.QueryContext,
   164  	lMeta, rMeta block.Metadata,
   165  	lIter, rIter block.StepIter,
   166  	controller *transform.Controller,
   167  	matching VectorMatching,
   168  	fn binaryFunction,
   169  ) (block.Block, error) {
   170  	if !matching.Set {
   171  		return nil, errNoMatching
   172  	}
   173  
   174  	if lIter.StepCount() != rIter.StepCount() {
   175  		return nil, errMismatchedStepCounts
   176  	}
   177  
   178  	lSeriesMeta := lIter.SeriesMeta()
   179  	lMeta, lSeriesMeta = removeNameTags(lMeta, lSeriesMeta)
   180  
   181  	rSeriesMeta := rIter.SeriesMeta()
   182  	rMeta, rSeriesMeta = removeNameTags(rMeta, rSeriesMeta)
   183  
   184  	lSeriesMeta = utils.FlattenMetadata(lMeta, lSeriesMeta)
   185  	rSeriesMeta = utils.FlattenMetadata(rMeta, rSeriesMeta)
   186  
   187  	takeLeft, correspondingRight, lSeriesMeta := intersect(matching,
   188  		lSeriesMeta, rSeriesMeta)
   189  	lMeta.Tags, lSeriesMeta = utils.DedupeMetadata(lSeriesMeta, lMeta.Tags.Opts)
   190  
   191  	lMeta.ResultMetadata = lMeta.ResultMetadata.
   192  		CombineMetadata(rMeta.ResultMetadata)
   193  	// Use metas from only taken left series
   194  	builder, err := controller.BlockBuilder(queryCtx, lMeta, lSeriesMeta)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	if err := builder.AddCols(lIter.StepCount()); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	for index := 0; lIter.Next() && rIter.Next(); index++ {
   204  		lStep := lIter.Current()
   205  		lValues := lStep.Values()
   206  		rStep := rIter.Current()
   207  		rValues := rStep.Values()
   208  
   209  		for seriesIdx, lIdx := range takeLeft {
   210  			rIdx := correspondingRight[seriesIdx]
   211  			lVal := lValues[lIdx]
   212  			rVal := rValues[rIdx]
   213  
   214  			if err := builder.AppendValue(index, fn(lVal, rVal)); err != nil {
   215  				return nil, err
   216  			}
   217  		}
   218  	}
   219  
   220  	if err = lIter.Err(); err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	if err = rIter.Err(); err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	return builder.Build(), nil
   229  }
   230  
   231  // intersect returns the slice of lhs indices that are shared with rhs,
   232  // the indices of the corresponding rhs values, and the metas for taken indices.
   233  func intersect(
   234  	matching VectorMatching,
   235  	lhs, rhs []block.SeriesMeta,
   236  ) ([]int, []int, []block.SeriesMeta) {
   237  	idFunction := hashFunc(matching.On, matching.MatchingLabels...)
   238  	// The set of signatures for the right-hand side.
   239  	rightSigs := make(map[uint64]int, len(rhs))
   240  	for idx, meta := range rhs {
   241  		rightSigs[idFunction(meta.Tags)] = idx
   242  	}
   243  
   244  	takeLeft := make([]int, 0, initIndexSliceLength)
   245  	correspondingRight := make([]int, 0, initIndexSliceLength)
   246  	leftMetas := make([]block.SeriesMeta, 0, initIndexSliceLength)
   247  
   248  	for lIdx, ls := range lhs {
   249  		// If there's a matching entry in the left-hand side Vector, add the sample.
   250  		id := idFunction(ls.Tags)
   251  		if rIdx, ok := rightSigs[id]; ok {
   252  			takeLeft = append(takeLeft, lIdx)
   253  			correspondingRight = append(correspondingRight, rIdx)
   254  			if matching.On && matching.Card == CardOneToOne && len(matching.MatchingLabels) > 0 {
   255  				ls.Tags = ls.Tags.TagsWithKeys(matching.MatchingLabels)
   256  			}
   257  			leftMetas = append(leftMetas, ls)
   258  		}
   259  	}
   260  
   261  	return takeLeft, correspondingRight, leftMetas
   262  }