github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/linear/sort.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 linear
    22  
    23  import (
    24  	"fmt"
    25  	"sort"
    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/functions/utils"
    30  	"github.com/m3db/m3/src/query/models"
    31  	"github.com/m3db/m3/src/query/parser"
    32  )
    33  
    34  const (
    35  	// SortType returns timeseries elements sorted by their values, in ascending order.
    36  	SortType = "sort"
    37  
    38  	// SortDescType is the same as sort, but sorts in descending order.
    39  	SortDescType = "sort_desc"
    40  )
    41  
    42  type sortOp struct {
    43  	opType string
    44  	lessFn lessFn
    45  }
    46  
    47  // OpType for the operator
    48  func (o sortOp) OpType() string {
    49  	return o.opType
    50  }
    51  
    52  // String representation
    53  func (o sortOp) String() string {
    54  	return fmt.Sprintf("type: %s", o.opType)
    55  }
    56  
    57  type sortNode struct {
    58  	op         sortOp
    59  	controller *transform.Controller
    60  }
    61  
    62  type valueAndMeta struct {
    63  	val        float64
    64  	seriesMeta block.SeriesMeta
    65  }
    66  
    67  type lessFn func (i, j float64) bool
    68  
    69  // Node creates an execution node
    70  func (o sortOp) Node(
    71  	controller *transform.Controller,
    72  	_ transform.Options,
    73  ) transform.OpNode {
    74  	return &sortNode{
    75  		op:         o,
    76  		controller: controller,
    77  	}
    78  }
    79  
    80  func (n *sortNode) Params() parser.Params {
    81  	return n.op
    82  }
    83  
    84  func (n *sortNode) Process(queryCtx *models.QueryContext, ID parser.NodeID, b block.Block) error {
    85  	return transform.ProcessSimpleBlock(n, n.controller, queryCtx, ID, b)
    86  }
    87  
    88  func (n *sortNode) ProcessBlock(queryCtx *models.QueryContext, ID parser.NodeID, b block.Block) (block.Block, error) {
    89  	if !queryCtx.Options.Instantaneous {
    90  		return b, nil
    91  	}
    92  	stepIter, err := b.StepIter()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	meta := b.Meta()
    98  	seriesMetas := utils.FlattenMetadata(meta, stepIter.SeriesMeta())
    99  	return n.processInstantBlock(queryCtx, stepIter, meta, seriesMetas)
   100  }
   101  
   102  func (n *sortNode) processInstantBlock(queryCtx *models.QueryContext, stepIter block.StepIter, meta block.Metadata, seriesMetas []block.SeriesMeta) (block.Block, error) {
   103  	ixLastStep := stepIter.StepCount() - 1 //we only care for the last step values for the instant query
   104  	for i := 0; i <= ixLastStep; i++ {
   105  		if !stepIter.Next() {
   106  			return nil, fmt.Errorf("invalid step count; expected %d got %d", stepIter.StepCount(), i+1)
   107  		}
   108  	}
   109  	values := stepIter.Current().Values()
   110  	meta.ResultMetadata.KeepNaNs = true
   111  	valuesToSort := make([]valueAndMeta, len(values))
   112  	for i, value := range values {
   113  		valuesToSort[i] = valueAndMeta{
   114  			val:        value,
   115  			seriesMeta: seriesMetas[i],
   116  		}
   117  	}
   118  
   119  	sort.Slice(valuesToSort, func(i, j int) bool {
   120  		return n.op.lessFn(valuesToSort[i].val, valuesToSort[j].val)
   121  	})
   122  
   123  	for i, sorted := range valuesToSort {
   124  		values[i] = sorted.val
   125  		seriesMetas[i] = sorted.seriesMeta
   126  	}
   127  
   128  	//adjust bounds to contain single step
   129  	time, err := meta.Bounds.TimeForIndex(ixLastStep)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	meta.Bounds = models.Bounds{
   134  		Start:    time,
   135  		Duration: meta.Bounds.StepSize,
   136  		StepSize: meta.Bounds.StepSize,
   137  	}
   138  
   139  	blockBuilder, err := n.controller.BlockBuilder(queryCtx, meta, seriesMetas)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	if err = blockBuilder.AddCols(1); err != nil {
   144  		return nil, err
   145  	}
   146  	if err := blockBuilder.AppendValues(0, values); err != nil {
   147  		return nil, err
   148  	}
   149  	if err = stepIter.Err(); err != nil {
   150  		return nil, err
   151  	}
   152  	return blockBuilder.Build(), nil
   153  }
   154  
   155  func NewSortOp(opType string) (parser.Params, error) {
   156  	ascending := opType == SortType
   157  	if !ascending && opType != SortDescType {
   158  		return nil, fmt.Errorf("operator not supported: %s", opType)
   159  	}
   160  
   161  	lessFn := utils.GreaterWithNaNs
   162  	if ascending {
   163  		lessFn = utils.LesserWithNaNs
   164  	}
   165  
   166  	return sortOp{opType, lessFn}, nil
   167  }