Files
opencloud/services/search/pkg/aggregation/validate.go
T
Dominik Schmidt b2d8a13c24 feat(search): add range aggregations
Numeric and date ranges on both engines: bleve folds them from doc values, OpenSearch uses range and date_range aggregations. The proto gains BucketRange and bucketDefinition.ranges, the aggregation package the range parser (bounds are numbers or RFC3339 dates, one kind per aggregation, anything else is rejected), the kind of an option and the validation of ranges against the field type. Every requested range is answered in request order, an empty one with a count of zero, with no space answering too; size does not cut ranges, keyAsNumber sorts them by their lower bound. Range keys read from..to. AGG-04, 05 and 07 to 09 in the parity suite.
2026-10-06 10:50:28 +02:00

58 lines
1.9 KiB
Go

package aggregation
import (
"fmt"
searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0"
"github.com/opencloud-eu/opencloud/services/search/pkg/mapping"
)
// ValidateOptions holds aggregations to what the index can answer: a field it
// knows, a kind that fits the type of the field. fieldType returns the mapping
// type of a field, empty for one a client may not aggregate on.
func ValidateOptions(opts []*searchsvc.AggregationOption, fieldType func(string) string) error {
for _, opt := range opts {
field, t := opt.GetField(), fieldType(opt.GetField())
if t == "" {
return fmt.Errorf("unknown aggregation field %q", field)
}
switch KindOf(opt) {
case KindRange:
parsed, err := ParseRanges(field, opt.GetBucketDefinition().GetRanges())
if err != nil {
return err
}
if err := validateRangeField(field, t, parsed); err != nil {
return err
}
case KindTerms:
if err := validateTermsField(field, t); err != nil {
return err
}
}
}
return nil
}
// validateTermsField allows the types whose values are buckets as they are.
// The values of a date are instants, they aggregate in ranges.
func validateTermsField(field, fieldType string) error {
switch fieldType {
case mapping.TypeKeyword, mapping.TypeBool, mapping.TypeNumeric:
return nil
case mapping.TypeDatetime:
return fmt.Errorf("terms aggregation is not supported on date field %q; use bucketDefinition.ranges", field)
}
return fmt.Errorf("terms aggregation is not supported on field %q", field)
}
func validateRangeField(field, fieldType string, ranges Ranges) error {
if len(ranges.Numeric) > 0 && fieldType != mapping.TypeNumeric {
return fmt.Errorf("numeric ranges need a numeric field, %q is none", field)
}
if len(ranges.Dates) > 0 && fieldType != mapping.TypeDatetime {
return fmt.Errorf("date ranges need a date field, %q is none", field)
}
return nil
}