github.com/m3db/m3@v1.5.0/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 }