200 lines
4.6 KiB
Go
200 lines
4.6 KiB
Go
package storageflux
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
|
|
"github.com/apache/arrow/go/arrow/memory"
|
|
"github.com/influxdata/flux"
|
|
"github.com/influxdata/flux/array"
|
|
"github.com/influxdata/flux/arrow"
|
|
"github.com/influxdata/flux/execute"
|
|
"github.com/influxdata/flux/values"
|
|
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
)
|
|
|
|
// splitWindows will split a windowTable by creating a new table from each
|
|
// row and modifying the group key to use the start and stop values from
|
|
// that row.
|
|
func splitWindows(ctx context.Context, alloc memory.Allocator, in flux.Table, selector bool, f func(t flux.Table) error) error {
|
|
wts := &windowTableSplitter{
|
|
ctx: ctx,
|
|
in: in,
|
|
alloc: alloc,
|
|
selector: selector,
|
|
}
|
|
return wts.Do(f)
|
|
}
|
|
|
|
type windowTableSplitter struct {
|
|
ctx context.Context
|
|
in flux.Table
|
|
alloc memory.Allocator
|
|
selector bool
|
|
}
|
|
|
|
func (w *windowTableSplitter) Do(f func(flux.Table) error) error {
|
|
defer w.in.Done()
|
|
|
|
startIdx, err := w.getTimeColumnIndex(execute.DefaultStartColLabel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stopIdx, err := w.getTimeColumnIndex(execute.DefaultStopColLabel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return w.in.Do(func(cr flux.ColReader) error {
|
|
// Retrieve the start and stop columns for splitting
|
|
// the windows.
|
|
start := cr.Times(startIdx)
|
|
stop := cr.Times(stopIdx)
|
|
|
|
// Iterate through each time to produce a table
|
|
// using the start and stop values.
|
|
arrs := make([]array.Interface, len(cr.Cols()))
|
|
for j := range cr.Cols() {
|
|
arrs[j] = getColumnValues(cr, j)
|
|
}
|
|
|
|
values := arrs[valueColIdx]
|
|
|
|
for i, n := 0, cr.Len(); i < n; i++ {
|
|
startT, stopT := start.Value(i), stop.Value(i)
|
|
|
|
// Rewrite the group key using the new time.
|
|
key := groupKeyForWindow(cr.Key(), startT, stopT)
|
|
if w.selector && values.IsNull(i) {
|
|
// Produce an empty table if the value is null
|
|
// and this is a selector.
|
|
table := execute.NewEmptyTable(key, cr.Cols())
|
|
if err := f(table); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Produce a slice for each column into a new
|
|
// table buffer.
|
|
buffer := arrow.TableBuffer{
|
|
GroupKey: key,
|
|
Columns: cr.Cols(),
|
|
Values: make([]array.Interface, len(cr.Cols())),
|
|
}
|
|
for j, arr := range arrs {
|
|
buffer.Values[j] = arrow.Slice(arr, int64(i), int64(i+1))
|
|
}
|
|
|
|
// Wrap these into a single table and execute.
|
|
done := make(chan struct{})
|
|
table := &windowTableRow{
|
|
buffer: buffer,
|
|
done: done,
|
|
}
|
|
if err := f(table); err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
case <-w.ctx.Done():
|
|
return w.ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (w *windowTableSplitter) getTimeColumnIndex(label string) (int, error) {
|
|
j := execute.ColIdx(label, w.in.Cols())
|
|
if j < 0 {
|
|
return -1, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: fmt.Sprintf("missing %q column from window splitter", label),
|
|
}
|
|
} else if c := w.in.Cols()[j]; c.Type != flux.TTime {
|
|
return -1, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: fmt.Sprintf("%q column must be of type time", label),
|
|
}
|
|
}
|
|
return j, nil
|
|
}
|
|
|
|
type windowTableRow struct {
|
|
used int32
|
|
buffer arrow.TableBuffer
|
|
done chan struct{}
|
|
}
|
|
|
|
func (w *windowTableRow) Key() flux.GroupKey {
|
|
return w.buffer.GroupKey
|
|
}
|
|
|
|
func (w *windowTableRow) Cols() []flux.ColMeta {
|
|
return w.buffer.Columns
|
|
}
|
|
|
|
func (w *windowTableRow) Do(f func(flux.ColReader) error) error {
|
|
if !atomic.CompareAndSwapInt32(&w.used, 0, 1) {
|
|
return &errors.Error{
|
|
Code: errors.EInternal,
|
|
Msg: "table already read",
|
|
}
|
|
}
|
|
defer close(w.done)
|
|
|
|
err := f(&w.buffer)
|
|
w.buffer.Release()
|
|
return err
|
|
}
|
|
|
|
func (w *windowTableRow) Done() {
|
|
if atomic.CompareAndSwapInt32(&w.used, 0, 1) {
|
|
w.buffer.Release()
|
|
close(w.done)
|
|
}
|
|
}
|
|
|
|
func (w *windowTableRow) Empty() bool {
|
|
return false
|
|
}
|
|
|
|
func groupKeyForWindow(key flux.GroupKey, start, stop int64) flux.GroupKey {
|
|
cols := key.Cols()
|
|
vs := make([]values.Value, len(cols))
|
|
for j, c := range cols {
|
|
if c.Label == execute.DefaultStartColLabel {
|
|
vs[j] = values.NewTime(values.Time(start))
|
|
} else if c.Label == execute.DefaultStopColLabel {
|
|
vs[j] = values.NewTime(values.Time(stop))
|
|
} else {
|
|
vs[j] = key.Value(j)
|
|
}
|
|
}
|
|
return execute.NewGroupKey(cols, vs)
|
|
}
|
|
|
|
// getColumnValues returns the array from the column reader as an array.Interface.
|
|
func getColumnValues(cr flux.ColReader, j int) array.Interface {
|
|
switch typ := cr.Cols()[j].Type; typ {
|
|
case flux.TInt:
|
|
return cr.Ints(j)
|
|
case flux.TUInt:
|
|
return cr.UInts(j)
|
|
case flux.TFloat:
|
|
return cr.Floats(j)
|
|
case flux.TString:
|
|
return cr.Strings(j)
|
|
case flux.TBool:
|
|
return cr.Bools(j)
|
|
case flux.TTime:
|
|
return cr.Times(j)
|
|
default:
|
|
panic(fmt.Errorf("unimplemented column type: %s", typ))
|
|
}
|
|
}
|