github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/block/column.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 block
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/uber-go/tally"
    28  
    29  	"github.com/m3db/m3/src/query/models"
    30  	xtime "github.com/m3db/m3/src/x/time"
    31  )
    32  
    33  type column struct {
    34  	Values []float64
    35  }
    36  
    37  // ColumnBlockBuilder builds a block optimized for column iteration.
    38  type ColumnBlockBuilder struct {
    39  	block           *columnBlock
    40  	blockDatapoints tally.Counter
    41  }
    42  
    43  type columnBlock struct {
    44  	blockType  BlockType
    45  	columns    []column
    46  	meta       Metadata
    47  	seriesMeta []SeriesMeta
    48  }
    49  
    50  func (c *columnBlock) Meta() Metadata {
    51  	return c.meta
    52  }
    53  
    54  func (c *columnBlock) StepIter() (StepIter, error) {
    55  	if len(c.columns) != c.meta.Bounds.Steps() {
    56  		return nil, fmt.
    57  			Errorf("mismatch in block columns and meta bounds, columns: %d, bounds: %v",
    58  				len(c.columns), c.meta.Bounds)
    59  	}
    60  
    61  	return &colBlockIter{
    62  		columns:    c.columns,
    63  		seriesMeta: c.seriesMeta,
    64  		meta:       c.meta,
    65  		idx:        -1,
    66  	}, nil
    67  }
    68  
    69  // SeriesIter is invalid for a columnar block.
    70  func (c *columnBlock) SeriesIter() (SeriesIter, error) {
    71  	return nil, errors.New("series iterator undefined for a scalar block")
    72  }
    73  
    74  // MultiSeriesIter is invalid for a columnar block.
    75  func (c *columnBlock) MultiSeriesIter(_ int) ([]SeriesIterBatch, error) {
    76  	return nil, errors.New("multi series iterator undefined for a scalar block")
    77  }
    78  
    79  func (c *columnBlock) SeriesMeta() []SeriesMeta {
    80  	return c.seriesMeta
    81  }
    82  
    83  func (c *columnBlock) StepCount() int {
    84  	return len(c.columns)
    85  }
    86  
    87  func (c *columnBlock) Info() BlockInfo {
    88  	return NewBlockInfo(c.blockType)
    89  }
    90  
    91  // Close frees up any resources
    92  // TODO: actually free up the resources
    93  func (c *columnBlock) Close() error {
    94  	return nil
    95  }
    96  
    97  type colBlockIter struct {
    98  	idx         int
    99  	timeForStep xtime.UnixNano
   100  	err         error
   101  	meta        Metadata
   102  	seriesMeta  []SeriesMeta
   103  	columns     []column
   104  }
   105  
   106  func (c *colBlockIter) SeriesMeta() []SeriesMeta {
   107  	return c.seriesMeta
   108  }
   109  
   110  func (c *colBlockIter) StepCount() int {
   111  	return len(c.columns)
   112  }
   113  
   114  func (c *colBlockIter) Next() bool {
   115  	if c.err != nil {
   116  		return false
   117  	}
   118  
   119  	c.idx++
   120  	next := c.idx < len(c.columns)
   121  	if !next {
   122  		return false
   123  	}
   124  
   125  	c.timeForStep, c.err = c.meta.Bounds.TimeForIndex(c.idx)
   126  	if c.err != nil {
   127  		return false
   128  	}
   129  
   130  	return next
   131  }
   132  
   133  func (c *colBlockIter) Err() error {
   134  	return c.err
   135  }
   136  
   137  func (c *colBlockIter) Current() Step {
   138  	col := c.columns[c.idx]
   139  	return ColStep{
   140  		time:   c.timeForStep,
   141  		values: col.Values,
   142  	}
   143  }
   144  
   145  func (c *colBlockIter) Close() { /*no-op*/ }
   146  
   147  // ColStep is a single column containing data from multiple series at a given time step
   148  type ColStep struct {
   149  	time   xtime.UnixNano
   150  	values []float64
   151  }
   152  
   153  // Time for the step
   154  func (c ColStep) Time() xtime.UnixNano {
   155  	return c.time
   156  }
   157  
   158  // Values for the column
   159  func (c ColStep) Values() []float64 {
   160  	return c.values
   161  }
   162  
   163  // NewColStep creates a new column step
   164  func NewColStep(t xtime.UnixNano, values []float64) Step {
   165  	return ColStep{time: t, values: values}
   166  }
   167  
   168  // NewColumnBlockBuilder creates a new column block builder
   169  func NewColumnBlockBuilder(
   170  	queryCtx *models.QueryContext,
   171  	meta Metadata,
   172  	seriesMeta []SeriesMeta) Builder {
   173  	return ColumnBlockBuilder{
   174  		blockDatapoints: queryCtx.Scope.Tagged(
   175  			map[string]string{"type": "generated"}).Counter("datapoints"),
   176  		block: &columnBlock{
   177  			meta:       meta,
   178  			seriesMeta: seriesMeta,
   179  			blockType:  BlockDecompressed,
   180  		},
   181  	}
   182  }
   183  
   184  // AppendValue adds a value to a column at index
   185  func (cb ColumnBlockBuilder) AppendValue(idx int, value float64) error {
   186  	columns := cb.block.columns
   187  	if len(columns) <= idx {
   188  		return fmt.Errorf("idx out of range for append: %d", idx)
   189  	}
   190  
   191  	cb.blockDatapoints.Inc(1)
   192  
   193  	columns[idx].Values = append(columns[idx].Values, value)
   194  	return nil
   195  }
   196  
   197  // AppendValues adds a slice of values to a column at index
   198  func (cb ColumnBlockBuilder) AppendValues(idx int, values []float64) error {
   199  	columns := cb.block.columns
   200  	if len(columns) <= idx {
   201  		return fmt.Errorf("idx out of range for append: %d", idx)
   202  	}
   203  
   204  	cb.blockDatapoints.Inc(int64(len(values)))
   205  	columns[idx].Values = append(columns[idx].Values, values...)
   206  	return nil
   207  }
   208  
   209  // AddCols adds the given number of columns to the block.
   210  func (cb ColumnBlockBuilder) AddCols(num int) error {
   211  	if num < 1 {
   212  		return fmt.Errorf("must add more than 0 columns, adding: %d", num)
   213  	}
   214  
   215  	newCols := make([]column, num)
   216  	cb.block.columns = append(cb.block.columns, newCols...)
   217  	return nil
   218  }
   219  
   220  // PopulateColumns sets all columns to the given row size.
   221  func (cb ColumnBlockBuilder) PopulateColumns(size int) {
   222  	cols := make([]float64, size*len(cb.block.columns))
   223  	for i := range cb.block.columns {
   224  		cb.block.columns[i] = column{Values: cols[size*i : size*(i+1)]}
   225  	}
   226  
   227  	// NB: initialize a clean series meta list with given cap and len,
   228  	// as row operations are done by arbitrary index.
   229  	cb.block.seriesMeta = make([]SeriesMeta, size)
   230  }
   231  
   232  // SetRow sets a given block row to the given values and metadata.
   233  func (cb ColumnBlockBuilder) SetRow(
   234  	idx int,
   235  	values []float64,
   236  	meta SeriesMeta,
   237  ) error {
   238  	cols := cb.block.columns
   239  	if len(values) == 0 {
   240  		// Sanity check. Should never happen.
   241  		return errors.New("cannot insert empty values")
   242  	}
   243  
   244  	if len(values) != len(cols) {
   245  		return fmt.Errorf("inserting column size %d does not match column size: %d",
   246  			len(values), len(cols))
   247  	}
   248  
   249  	rows := len(cols[0].Values)
   250  	if idx < 0 || idx >= rows {
   251  		return fmt.Errorf("cannot insert into row %d, have %d rows", idx, rows)
   252  	}
   253  
   254  	for i, v := range values {
   255  		cb.block.columns[i].Values[idx] = v
   256  	}
   257  
   258  	cb.block.seriesMeta[idx] = meta
   259  	return nil
   260  }
   261  
   262  // Build builds the block.
   263  func (cb ColumnBlockBuilder) Build() Block {
   264  	return cb.block
   265  }
   266  
   267  // BuildAsType builds the block, forcing it to the given BlockType.
   268  func (cb ColumnBlockBuilder) BuildAsType(blockType BlockType) Block {
   269  	cb.block.blockType = blockType
   270  	return cb.block
   271  }