blob: c520a39d89048afd3ce52f2d684305563189d607 [file]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Package aggregation implements aggregation functions to statistic a range of values.
package aggregation
import (
"math"
"github.com/pkg/errors"
modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
)
var (
errUnknownFunc = errors.New("unknown aggregation function")
errUnSupportedFieldType = errors.New("unsupported field type")
)
// Partial represents the intermediate result of a Map phase.
// For most functions only Value is meaningful; for MEAN both Value (sum) and Count are used.
type Partial[N Number] struct {
Value N
Count N
}
// Map accumulates raw values and produces aggregation results.
// It serves as the local accumulator for raw data points.
type Map[N Number] interface {
In(N)
Val() N
Partial() Partial[N]
Reset()
}
// Reduce combines intermediate results from Map phases into a final value.
type Reduce[N Number] interface {
Combine(Partial[N])
Val() N
Reset()
}
// Number denotes the supported number types.
type Number interface {
~int64 | ~float64
}
// NewMap returns a Map aggregation function for the given type.
func NewMap[N Number](af modelv1.AggregationFunction) (Map[N], error) {
var result Map[N]
switch af {
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MEAN:
result = &meanFunc[N]{zero: zero[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_COUNT:
result = &countFunc[N]{zero: zero[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MAX:
result = &maxFunc[N]{min: minOf[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MIN:
result = &minFunc[N]{max: maxOf[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_SUM:
result = &sumFunc[N]{zero: zero[N]()}
default:
return nil, errors.WithMessagef(errUnknownFunc, "unknown function:%s", modelv1.AggregationFunction_name[int32(af)])
}
result.Reset()
return result, nil
}
// NewReduce returns a Reduce aggregation function for the given type.
func NewReduce[N Number](af modelv1.AggregationFunction) (Reduce[N], error) {
var result Reduce[N]
switch af {
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MEAN:
result = &meanReduceFunc[N]{zero: zero[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_COUNT:
result = &countReduceFunc[N]{zero: zero[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MAX:
result = &maxReduceFunc[N]{min: minOf[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_MIN:
result = &minReduceFunc[N]{max: maxOf[N]()}
case modelv1.AggregationFunction_AGGREGATION_FUNCTION_SUM:
result = &sumReduceFunc[N]{zero: zero[N]()}
default:
return nil, errors.WithMessagef(errUnknownFunc, "unknown function:%s", modelv1.AggregationFunction_name[int32(af)])
}
result.Reset()
return result, nil
}
// FromFieldValue transforms modelv1.FieldValue to Number.
func FromFieldValue[N Number](fieldValue *modelv1.FieldValue) (N, error) {
switch fieldValue.GetValue().(type) {
case *modelv1.FieldValue_Int:
return N(fieldValue.GetInt().Value), nil
case *modelv1.FieldValue_Float:
return N(fieldValue.GetFloat().Value), nil
}
return zero[N](), errUnSupportedFieldType
}
// ToFieldValue transforms Number to modelv1.FieldValue.
func ToFieldValue[N Number](value N) (*modelv1.FieldValue, error) {
switch any(value).(type) {
case int64:
return &modelv1.FieldValue{Value: &modelv1.FieldValue_Int{Int: &modelv1.Int{Value: int64(value)}}}, nil
case float64:
return &modelv1.FieldValue{Value: &modelv1.FieldValue_Float{Float: &modelv1.Float{Value: float64(value)}}}, nil
}
return nil, errUnSupportedFieldType
}
// PartialToFieldValues converts a Partial to field values for wire transport.
// For MEAN it returns two values (Value/sum first, Count second); for others one value.
func PartialToFieldValues[N Number](af modelv1.AggregationFunction, p Partial[N]) ([]*modelv1.FieldValue, error) {
if af == modelv1.AggregationFunction_AGGREGATION_FUNCTION_MEAN {
vFv, err := ToFieldValue(p.Value)
if err != nil {
return nil, err
}
cFv, err := ToFieldValue(p.Count)
if err != nil {
return nil, err
}
return []*modelv1.FieldValue{vFv, cFv}, nil
}
vFv, err := ToFieldValue(p.Value)
if err != nil {
return nil, err
}
return []*modelv1.FieldValue{vFv}, nil
}
// FieldValuesToPartial converts field values from wire transport to a Partial.
// For MEAN expects two values (sum, count); for others one value (Count will be zero).
func FieldValuesToPartial[N Number](af modelv1.AggregationFunction, fvs []*modelv1.FieldValue) (Partial[N], error) {
var p Partial[N]
if len(fvs) == 0 {
return p, nil
}
v, err := FromFieldValue[N](fvs[0])
if err != nil {
return p, err
}
p.Value = v
if af == modelv1.AggregationFunction_AGGREGATION_FUNCTION_MEAN && len(fvs) >= 2 {
c, err := FromFieldValue[N](fvs[1])
if err != nil {
return p, err
}
p.Count = c
}
return p, nil
}
func minOf[N Number]() (r N) {
switch x := any(&r).(type) {
case *int64:
*x = math.MinInt64
case *float64:
*x = -math.MaxFloat64
default:
panic("unreachable")
}
return
}
func maxOf[N Number]() (r N) {
switch x := any(&r).(type) {
case *int64:
*x = math.MaxInt64
case *float64:
*x = math.MaxFloat64
default:
panic("unreachable")
}
return
}
func zero[N Number]() N {
var z N
return z
}