feat(search): add nested aggregations

Sub-aggregations on both engines: bleve folds child buckets below their parent through the same collector, OpenSearch nests them natively, so a request stays one search per space. The proto gains sub_aggregations on the option and on the bucket; merge, finalize, validation and the bucket limit walk every level, results are positional at every level, a metric has no buckets to nest in. AGG-10 to AGG-15, 20, 35, 40 and 44 in the parity suite, AGG-14 as a known OpenSearch divergence.
This commit is contained in:
Dominik Schmidt committed 2026-10-05 12:10:55 +02:00
1 parent 1ae248bae7
commit 993951430b
17 files changed
+603 -207

No files matched your search

@@ -478,6 +478,9 @@ type AggregationOption struct {
// rather than a bucket aggregation; the corresponding AggregationResult
// carries `metric` instead of `buckets`.
MetricDefinition *MetricDefinition `protobuf:"bytes,4,opt,name=metric_definition,json=metricDefinition,proto3" json:"metric_definition,omitempty"`
// Optional. Nested aggregations computed within each bucket of this
// aggregation.
SubAggregations []*AggregationOption `protobuf:"bytes,5,rep,name=sub_aggregations,json=subAggregations,proto3" json:"sub_aggregations,omitempty"`
}
func (x *AggregationOption) Reset() {
@@ -540,6 +543,13 @@ func (x *AggregationOption) GetMetricDefinition() *MetricDefinition {
return nil
}
func (x *AggregationOption) GetSubAggregations() []*AggregationOption {
if x != nil {
return x.SubAggregations
}
return nil
}
type MetricDefinition struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -886,6 +896,9 @@ type Bucket struct {
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
// Nested aggregation results, one per sub_aggregation of the parent
// AggregationOption, at the same position.
SubAggregations []*AggregationResult `protobuf:"bytes,3,rep,name=sub_aggregations,json=subAggregations,proto3" json:"sub_aggregations,omitempty"`
}
func (x *Bucket) Reset() {
@@ -934,6 +947,13 @@ func (x *Bucket) GetCount() int64 {
return 0
}
func (x *Bucket) GetSubAggregations() []*AggregationResult {
if x != nil {
return x.SubAggregations
}
return nil
}
type IndexSpaceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1179,7 +1199,7 @@ var file_opencloud_services_search_v0_search_proto_rawDesc = []byte{
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e,
0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x52, 0x0c, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
0x89, 0x02, 0x0a, 0x11, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f,
0xeb, 0x02, 0x0a, 0x11, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x04, 0x73,
0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52,
@@ -1195,149 +1215,161 @@ var file_opencloud_services_search_v0_search_proto_rawDesc = []byte{
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x10, 0x6d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x10, 0x4d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x3c, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e,
0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74,
0x72, 0x69, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xe4, 0x01,
0x0a, 0x10, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x63, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x60, 0x0a, 0x10, 0x73,
0x75, 0x62, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x0f, 0x73, 0x75,
0x62, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x50, 0x0a,
0x10, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x3c, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22,
0xe4, 0x01, 0x0a, 0x10, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2e, 0x76, 0x30, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x42,
0x79, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x73, 0x5f,
0x64, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0c, 0x69, 0x73, 0x44, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23,
0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x52,
0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x64, 0x65,
0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c,
0x69, 0x73, 0x44, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d,
0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x6f, 0x75, 0x6e,
0x74, 0x12, 0x41, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30,
0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x72, 0x61,
0x6e, 0x67, 0x65, 0x73, 0x22, 0x31, 0x0a, 0x0b, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x61,
0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0xa7, 0x01, 0x0a, 0x11, 0x41, 0x67, 0x67, 0x72,
0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a,
0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x12, 0x3e, 0x0a, 0x07, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x07, 0x62, 0x75, 0x63, 0x6b,
0x65, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x22, 0xb7, 0x01, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x3c, 0x0a, 0x04,
0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65,
0x76, 0x30, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06,
0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0x31, 0x0a, 0x0b, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74,
0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0xa7, 0x01, 0x0a, 0x11, 0x41, 0x67,
0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12,
0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x3e, 0x0a, 0x07, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f,
0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72,
0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x07, 0x62, 0x75,
0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74,
0x72, 0x69, 0x63, 0x22, 0xb7, 0x01, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x3c,
0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x6f,
0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x75, 0x6d, 0x18, 0x03,
0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x73, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x69,
0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03,
0x6d, 0x61, 0x78, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8c, 0x01,
0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x12, 0x5a, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01,
0x28, 0x01, 0x52, 0x03, 0x73, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a,
0x03, 0x6d, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12,
0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x61,
0x78, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x30, 0x0a, 0x06, 0x42,
0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x94, 0x01,
0x0a, 0x11, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17,
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65,
0x5f, 0x72, 0x65, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c,
0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0b,
0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
0x05, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x63, 0x79, 0x22, 0xd1, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70,
0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73,
0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73,
0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x70, 0x61, 0x63, 0x65,
0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x64, 0x65,
0x78, 0x65, 0x64, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12,
0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18,
0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x70, 0x61, 0x63,
0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2a, 0x7d, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43,
0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49,
0x4e, 0x44, 0x5f, 0x53, 0x55, 0x4d, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52,
0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x13, 0x0a,
0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x41, 0x58,
0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e,
0x44, 0x5f, 0x41, 0x56, 0x47, 0x10, 0x04, 0x2a, 0x8c, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x63, 0x6b,
0x65, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x55, 0x43, 0x4b,
0x45, 0x54, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x55, 0x43, 0x4b,
0x45, 0x54, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54,
0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53, 0x4f, 0x52,
0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49,
0x4e, 0x47, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53,
0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x53, 0x5f, 0x4e, 0x55,
0x4d, 0x42, 0x45, 0x52, 0x10, 0x03, 0x32, 0xb3, 0x02, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63,
0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x85, 0x01, 0x0a, 0x06, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x2c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30,
0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x12, 0x98, 0x01, 0x0a, 0x0a, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65,
0x12, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0f, 0x73, 0x75, 0x62,
0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x94, 0x01, 0x0a,
0x11, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a,
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f,
0x72, 0x65, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66,
0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0b, 0x63,
0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
0x42, 0x04, 0xe2, 0x41, 0x01, 0x01, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,
0x6e, 0x63, 0x79, 0x22, 0xd1, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61,
0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70,
0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70,
0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x64,
0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x44,
0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x65, 0x64, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x21,
0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x70, 0x61, 0x63, 0x65,
0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2a, 0x7d, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f,
0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e,
0x44, 0x5f, 0x53, 0x55, 0x4d, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49,
0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f,
0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x41, 0x58, 0x10,
0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x4b, 0x49, 0x4e, 0x44,
0x5f, 0x41, 0x56, 0x47, 0x10, 0x04, 0x2a, 0x8c, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x63, 0x6b, 0x65,
0x74, 0x53, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x55, 0x43, 0x4b, 0x45,
0x54, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x55, 0x43, 0x4b, 0x45,
0x54, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10,
0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53, 0x4f, 0x52, 0x54,
0x5f, 0x42, 0x59, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e,
0x47, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53, 0x4f,
0x52, 0x54, 0x5f, 0x42, 0x59, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x53, 0x5f, 0x4e, 0x55, 0x4d,
0x42, 0x45, 0x52, 0x10, 0x03, 0x32, 0xb3, 0x02, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x85, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x61,
0x72, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x30, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30,
0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, 0x1a,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x69,
0x6e, 0x64, 0x65, 0x78, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x30, 0x01, 0x32, 0xa7, 0x01, 0x0a,
0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x95,
0x01, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x30, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x70,
0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63,
0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2f,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x42, 0xf2, 0x02, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d,
0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x65, 0x61, 0x72,
0x63, 0x68, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa2, 0x02, 0x12, 0xb7, 0x01, 0x0a, 0x10, 0x4f, 0x70,
0x65, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0x51,
0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48,
0x12, 0x29, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x12, 0x98, 0x01, 0x0a, 0x0a, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x12,
0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x30, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, 0x1a, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x30, 0x01, 0x32, 0xa7, 0x01, 0x0a, 0x0d,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x95, 0x01,
0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x30, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63,
0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x65,
0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2f, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x42, 0xf2, 0x02, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65,
0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x1a, 0x14, 0x73, 0x75, 0x70,
0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65,
0x75, 0x2a, 0x49, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12,
0x3b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f,
0x75, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa2, 0x02, 0x12, 0xb7, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65,
0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0x51, 0x0a,
0x0e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12,
0x29, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75,
0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f,
0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e,
0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3e, 0x0a, 0x10, 0x44,
0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12,
0x2a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2e, 0x6f, 0x70,
0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65, 0x75, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70,
0x6f, 0x72, 0x74, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65, 0x75,
0x2a, 0x49, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x3b,
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f,
0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d,
0x61, 0x69, 0x6e, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30,
0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3e, 0x0a, 0x10, 0x44, 0x65,
0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2a,
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65, 0x75, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
@@ -1385,24 +1417,26 @@ var file_opencloud_services_search_v0_search_proto_depIdxs = []int32{
10, // 7: opencloud.services.search.v0.SearchIndexResponse.aggregations:type_name -> opencloud.services.search.v0.AggregationResult
8, // 8: opencloud.services.search.v0.AggregationOption.bucket_definition:type_name -> opencloud.services.search.v0.BucketDefinition
7, // 9: opencloud.services.search.v0.AggregationOption.metric_definition:type_name -> opencloud.services.search.v0.MetricDefinition
0, // 10: opencloud.services.search.v0.MetricDefinition.kind:type_name -> opencloud.services.search.v0.MetricKind
1, // 11: opencloud.services.search.v0.BucketDefinition.sort_by:type_name -> opencloud.services.search.v0.BucketSortBy
9, // 12: opencloud.services.search.v0.BucketDefinition.ranges:type_name -> opencloud.services.search.v0.BucketRange
12, // 13: opencloud.services.search.v0.AggregationResult.buckets:type_name -> opencloud.services.search.v0.Bucket
11, // 14: opencloud.services.search.v0.AggregationResult.metric:type_name -> opencloud.services.search.v0.Metric
0, // 15: opencloud.services.search.v0.Metric.kind:type_name -> opencloud.services.search.v0.MetricKind
17, // 16: opencloud.services.search.v0.IndexSpaceResponse.space_duration:type_name -> google.protobuf.Duration
2, // 17: opencloud.services.search.v0.SearchProvider.Search:input_type -> opencloud.services.search.v0.SearchRequest
13, // 18: opencloud.services.search.v0.SearchProvider.IndexSpace:input_type -> opencloud.services.search.v0.IndexSpaceRequest
4, // 19: opencloud.services.search.v0.IndexProvider.Search:input_type -> opencloud.services.search.v0.SearchIndexRequest
3, // 20: opencloud.services.search.v0.SearchProvider.Search:output_type -> opencloud.services.search.v0.SearchResponse
14, // 21: opencloud.services.search.v0.SearchProvider.IndexSpace:output_type -> opencloud.services.search.v0.IndexSpaceResponse
5, // 22: opencloud.services.search.v0.IndexProvider.Search:output_type -> opencloud.services.search.v0.SearchIndexResponse
20, // [20:23] is the sub-list for method output_type
17, // [17:20] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
17, // [17:17] is the sub-list for extension extendee
0, // [0:17] is the sub-list for field type_name
6, // 10: opencloud.services.search.v0.AggregationOption.sub_aggregations:type_name -> opencloud.services.search.v0.AggregationOption
0, // 11: opencloud.services.search.v0.MetricDefinition.kind:type_name -> opencloud.services.search.v0.MetricKind
1, // 12: opencloud.services.search.v0.BucketDefinition.sort_by:type_name -> opencloud.services.search.v0.BucketSortBy
9, // 13: opencloud.services.search.v0.BucketDefinition.ranges:type_name -> opencloud.services.search.v0.BucketRange
12, // 14: opencloud.services.search.v0.AggregationResult.buckets:type_name -> opencloud.services.search.v0.Bucket
11, // 15: opencloud.services.search.v0.AggregationResult.metric:type_name -> opencloud.services.search.v0.Metric
0, // 16: opencloud.services.search.v0.Metric.kind:type_name -> opencloud.services.search.v0.MetricKind
10, // 17: opencloud.services.search.v0.Bucket.sub_aggregations:type_name -> opencloud.services.search.v0.AggregationResult
17, // 18: opencloud.services.search.v0.IndexSpaceResponse.space_duration:type_name -> google.protobuf.Duration
2, // 19: opencloud.services.search.v0.SearchProvider.Search:input_type -> opencloud.services.search.v0.SearchRequest
13, // 20: opencloud.services.search.v0.SearchProvider.IndexSpace:input_type -> opencloud.services.search.v0.IndexSpaceRequest
4, // 21: opencloud.services.search.v0.IndexProvider.Search:input_type -> opencloud.services.search.v0.SearchIndexRequest
3, // 22: opencloud.services.search.v0.SearchProvider.Search:output_type -> opencloud.services.search.v0.SearchResponse
14, // 23: opencloud.services.search.v0.SearchProvider.IndexSpace:output_type -> opencloud.services.search.v0.IndexSpaceResponse
5, // 24: opencloud.services.search.v0.IndexProvider.Search:output_type -> opencloud.services.search.v0.SearchIndexResponse
22, // [22:25] is the sub-list for method output_type
19, // [19:22] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
}
func init() { file_opencloud_services_search_v0_search_proto_init() }
@@ -186,6 +186,13 @@
"metricDefinition": {
"$ref": "#/definitions/v0MetricDefinition",
"description": "Optional. When set, this aggregation is a scalar metric over `field`\nrather than a bucket aggregation; the corresponding AggregationResult\ncarries `metric` instead of `buckets`."
},
"subAggregations": {
"type": "array",
"items": {
"$ref": "#/definitions/v0AggregationOption"
},
"description": "Optional. Nested aggregations computed within each bucket of this\naggregation."
}
}
},
@@ -277,6 +284,13 @@
"count": {
"type": "string",
"format": "int64"
},
"subAggregations": {
"type": "array",
"items": {
"$ref": "#/definitions/v0AggregationResult"
},
"description": "Nested aggregation results, one per sub_aggregation of the parent\nAggregationOption, at the same position."
}
}
},
@@ -138,6 +138,9 @@ message AggregationOption {
// rather than a bucket aggregation; the corresponding AggregationResult
// carries `metric` instead of `buckets`.
MetricDefinition metric_definition = 4 [(google.api.field_behavior) = OPTIONAL];
// Optional. Nested aggregations computed within each bucket of this
// aggregation.
repeated AggregationOption sub_aggregations = 5 [(google.api.field_behavior) = OPTIONAL];
}
message MetricDefinition {
@@ -205,6 +208,9 @@ message Metric {
message Bucket {
string key = 1;
int64 count = 2;
// Nested aggregation results, one per sub_aggregation of the parent
// AggregationOption, at the same position.
repeated AggregationResult sub_aggregations = 3;
}
message IndexSpaceRequest {
+13 -6
View File
@@ -92,13 +92,17 @@ func unsupportedProperty(sr libregraph.SearchRequest) string {
if len(sr.AggregationFilters) > 0 {
return "aggregationFilters"
}
for _, a := range sr.Aggregations {
switch {
case a.LibreGraphGeohashDefinition != nil:
return "@libre.graph.geohashDefinition"
case len(a.LibreGraphSubAggregations) > 0:
return "@libre.graph.subAggregations"
var geohash func(aggs []libregraph.AggregationOption) bool
geohash = func(aggs []libregraph.AggregationOption) bool {
for _, a := range aggs {
if a.LibreGraphGeohashDefinition != nil || geohash(a.LibreGraphSubAggregations) {
return true
}
}
return false
}
if geohash(sr.Aggregations) {
return "@libre.graph.geohashDefinition"
}
return ""
}
@@ -243,6 +247,9 @@ func validateAggregations(aggs []libregraph.AggregationOption) error {
return fmt.Errorf("minimumCount of the aggregation on %q must not be negative", a.Field)
}
}
if err := validateAggregations(a.LibreGraphSubAggregations); err != nil {
return err
}
}
return nil
}
@@ -55,6 +55,7 @@ func indexField(field string) string {
func resolveFields(opts []*searchsvc.AggregationOption) {
for _, opt := range opts {
opt.Field = indexField(opt.Field)
resolveFields(opt.SubAggregations)
}
}
@@ -68,6 +69,9 @@ func libregraphAggregationsToSearch(in []libregraph.AggregationOption) []*search
if a.BucketDefinition != nil {
agg.BucketDefinition = libregraphBucketDefinitionToSearch(*a.BucketDefinition)
}
if len(a.LibreGraphSubAggregations) > 0 {
agg.SubAggregations = libregraphAggregationsToSearch(a.LibreGraphSubAggregations)
}
if md := a.LibreGraphMetricDefinition; md != nil {
agg.MetricDefinition = &searchsvc.MetricDefinition{Kind: metricKinds[md.Kind]}
}
@@ -99,8 +103,8 @@ func libregraphRangesToSearch(in []libregraph.BucketAggregationRange) []*searchs
}
// searchAggregationsToLibregraph maps the results to their definitions by
// position: the search service answers one result per aggregation, in request
// order.
// position, at every level: the search service answers one result per
// aggregation, in request order.
func searchAggregationsToLibregraph(in []*searchsvc.AggregationResult, defs []libregraph.AggregationOption) []libregraph.SearchAggregation {
if len(in) == 0 {
return nil
@@ -123,8 +127,9 @@ func searchAggregationsToLibregraph(in []*searchsvc.AggregationResult, defs []li
agg.Buckets = make([]libregraph.SearchBucket, 0, len(in[i].GetBuckets()))
for _, b := range in[i].GetBuckets() {
agg.Buckets = append(agg.Buckets, libregraph.SearchBucket{
Key: libregraph.PtrString(b.GetKey()),
Count: libregraph.PtrInt64(b.GetCount()),
Key: libregraph.PtrString(b.GetKey()),
Count: libregraph.PtrInt64(b.GetCount()),
LibreGraphSubAggregations: searchAggregationsToLibregraph(b.GetSubAggregations(), def.LibreGraphSubAggregations),
})
}
out = append(out, agg)
@@ -92,8 +92,9 @@ type searchAggregationJSON struct {
Value *float64 `json:"value"`
} `json:"@libre.graph.metric"`
Buckets []struct {
Key string `json:"key"`
Count int64 `json:"count"`
Key string `json:"key"`
Count int64 `json:"count"`
SubAggregations []searchAggregationJSON `json:"@libre.graph.subAggregations"`
} `json:"buckets"`
}
@@ -309,6 +310,8 @@ var _ = ginkgo.Describe("SearchQuery", func() {
ginkgo.Entry("an unknown sortBy", `"aggregations": [{"field": "audio.artist", "bucketDefinition": {"sortBy": "relevance"}}]`),
ginkgo.Entry("a size below one", `"aggregations": [{"field": "audio.artist", "size": 0}]`),
ginkgo.Entry("a negative minimumCount", `"aggregations": [{"field": "audio.artist", "bucketDefinition": {"sortBy": "count", "minimumCount": -1}}]`),
ginkgo.Entry("an unknown metric kind two levels down",
`"aggregations": [{"field": "audio.artist", "@libre.graph.subAggregations": [{"field": "audio.album", "@libre.graph.subAggregations": [{"field": "audio.year", "@libre.graph.metricDefinition": {"kind": "median"}}]}]}]`),
ginkgo.Entry("a field the index does not know", `"aggregations": [{"field": "audio.nonexistent"}]`),
ginkgo.Entry("a range bound that is no number", `"aggregations": [{"field": "audio.year", "bucketDefinition": {"sortBy": "count", "ranges": [{"from": "1970", "to": "198o"}]}}]`),
)
@@ -407,7 +410,8 @@ var _ = ginkgo.Describe("SearchQuery", func() {
ginkgo.Entry("aggregationFilters", `"aggregationFilters": ["audio.artist:\"ǂǂ5361786f6e\""]`, "aggregationFilters"),
ginkgo.Entry("a geohash aggregation",
`"aggregations": [{"field": "location", "@libre.graph.geohashDefinition": {"precision": 5}}]`, "geohashDefinition"),
ginkgo.Entry("sub-aggregations", `"aggregations": [{"field": "audio.artist", "@libre.graph.subAggregations": [{"field": "audio.album"}]}]`, "subAggregations"),
ginkgo.Entry("a nested geohash aggregation",
`"aggregations": [{"field": "audio.artist", "@libre.graph.subAggregations": [{"field": "location", "@libre.graph.geohashDefinition": {"precision": 5}}]}]`, "geohashDefinition"),
)
ginkgo.It("rejects an $expand it does not know with 400", func() {
@@ -32,8 +32,8 @@ func KindOf(opt *searchsvc.AggregationOption) Kind {
}
// MaxBuckets is how many buckets the aggregations of one request may have in
// one space: the search.max_buckets default of OpenSearch, held on every
// engine.
// one space, all levels counted: the search.max_buckets default of OpenSearch,
// held on every engine.
const MaxBuckets = math.MaxUint16
// ErrTooManyBuckets is what an engine answers beyond MaxBuckets; the service
@@ -52,6 +52,9 @@ func countBuckets(results []*searchsvc.AggregationResult) int {
n := 0
for _, r := range results {
n += len(r.GetBuckets())
for _, b := range r.GetBuckets() {
n += countBuckets(b.GetSubAggregations())
}
}
return n
}
@@ -11,6 +11,17 @@ import (
)
var _ = Describe("CheckBuckets", func() {
// artists of an album each: twice as many buckets as artists
artistsWithAlbum := func(artists int) []*searchsvc.AggregationResult {
res := &searchsvc.AggregationResult{Field: "audio.artist"}
for i := range artists {
res.Buckets = append(res.Buckets, &searchsvc.Bucket{Key: strconv.Itoa(i), Count: 1, SubAggregations: []*searchsvc.AggregationResult{
{Field: "audio.album", Buckets: []*searchsvc.Bucket{{Key: "Singles", Count: 1}}},
}})
}
return []*searchsvc.AggregationResult{res}
}
artists := func(count int) []*searchsvc.AggregationResult {
res := &searchsvc.AggregationResult{Field: "audio.artist"}
for i := range count {
@@ -25,4 +36,9 @@ var _ = Describe("CheckBuckets", func() {
Expect(aggregation.CheckBuckets(artists(65535))).To(Succeed())
Expect(aggregation.CheckBuckets(artists(65536))).To(MatchError(aggregation.ErrTooManyBuckets))
})
It("counts the buckets of all levels", func() {
Expect(aggregation.CheckBuckets(artistsWithAlbum(32767))).To(Succeed(), "65534 buckets")
Expect(aggregation.CheckBuckets(artistsWithAlbum(32768))).To(MatchError(aggregation.ErrTooManyBuckets), "65536 buckets")
})
})
+13 -6
View File
@@ -29,14 +29,15 @@ func Empty(opts []*searchsvc.AggregationOption) []*searchsvc.AggregationResult {
}
// RangeBuckets lists every range of a range aggregation in request order: the
// answered bucket where there is one, a count of zero otherwise.
// answered bucket where there is one, a count of zero and empty
// sub-aggregations otherwise.
func RangeBuckets(opt *searchsvc.AggregationOption, answered map[string]*searchsvc.Bucket) []*searchsvc.Bucket {
ranges := opt.GetBucketDefinition().GetRanges()
out := make([]*searchsvc.Bucket, 0, len(ranges))
for _, r := range ranges {
bucket, ok := answered[RangeKey(r)]
if !ok {
bucket = &searchsvc.Bucket{Key: RangeKey(r)}
bucket = &searchsvc.Bucket{Key: RangeKey(r), SubAggregations: Empty(opt.GetSubAggregations())}
}
out = append(out, bucket)
}
@@ -44,8 +45,8 @@ func RangeBuckets(opt *searchsvc.AggregationOption, answered map[string]*searchs
}
// Merge folds the results of one space into acc and returns it. Results
// belong to the option at their position, so several aggregations on one
// field stay apart.
// belong to the option at their position, at every level, so several
// aggregations on one field stay apart.
func Merge(opts []*searchsvc.AggregationOption, acc, results []*searchsvc.AggregationResult) []*searchsvc.AggregationResult {
if acc == nil {
acc = Empty(opts)
@@ -70,13 +71,16 @@ func Merge(opts []*searchsvc.AggregationOption, acc, results []*searchsvc.Aggreg
acc[i].Buckets = append(acc[i].Buckets, merged)
}
merged.Count += b.GetCount()
if subs := opt.GetSubAggregations(); len(subs) > 0 {
merged.SubAggregations = Merge(subs, merged.GetSubAggregations(), b.GetSubAggregations())
}
}
}
return acc
}
// Finalize shapes merged results per their options: buckets get their minimum
// count, order and size, metrics their value.
// Finalize shapes merged results per their options, at every level: buckets
// get their minimum count, order and size, metrics their value.
func Finalize(opts []*searchsvc.AggregationOption, results []*searchsvc.AggregationResult) {
for i, opt := range opts {
r := results[i]
@@ -87,6 +91,9 @@ func Finalize(opts []*searchsvc.AggregationOption, results []*searchsvc.Aggregat
continue
}
r.Buckets = shapeBuckets(opt, r.GetBuckets())
for _, b := range r.GetBuckets() {
Finalize(opt.GetSubAggregations(), b.GetSubAggregations())
}
}
}
+79 -17
View File
@@ -31,8 +31,8 @@ func buckets(field string, bs ...*searchsvc.Bucket) *searchsvc.AggregationResult
return &searchsvc.AggregationResult{Field: field, Buckets: bs}
}
func bucket(key string, count int64) *searchsvc.Bucket {
return &searchsvc.Bucket{Key: key, Count: count}
func bucket(key string, count int64, subs ...*searchsvc.AggregationResult) *searchsvc.Bucket {
return &searchsvc.Bucket{Key: key, Count: count, SubAggregations: subs}
}
func rangesOf(sortBy searchsvc.BucketSortBy, rs ...*searchsvc.BucketRange) *searchsvc.BucketDefinition {
@@ -40,23 +40,29 @@ func rangesOf(sortBy searchsvc.BucketSortBy, rs ...*searchsvc.BucketRange) *sear
}
// render flattens results like the parity suite: one line per bucket or
// metric.
// metric, nested ones below their bucket.
func render(results []*searchsvc.AggregationResult) []string {
out := []string{}
for _, r := range results {
if m := r.GetMetric(); m != nil {
if m.Value == nil {
out = append(out, fmt.Sprintf("%s %s none", r.GetField(), m.GetKind()))
} else {
out = append(out, fmt.Sprintf("%s %s=%v", r.GetField(), m.GetKind(), m.GetValue()))
var walk func(prefix string, results []*searchsvc.AggregationResult) []string
walk = func(prefix string, results []*searchsvc.AggregationResult) []string {
out := []string{}
for _, r := range results {
if m := r.GetMetric(); m != nil {
if m.Value == nil {
out = append(out, prefix+fmt.Sprintf("%s %s none", r.GetField(), m.GetKind()))
} else {
out = append(out, prefix+fmt.Sprintf("%s %s=%v", r.GetField(), m.GetKind(), m.GetValue()))
}
continue
}
for _, b := range r.GetBuckets() {
line := prefix + fmt.Sprintf("%s %s=%d", r.GetField(), b.GetKey(), b.GetCount())
out = append(out, line)
out = append(out, walk(line+" / ", b.GetSubAggregations())...)
}
continue
}
for _, b := range r.GetBuckets() {
out = append(out, fmt.Sprintf("%s %s=%d", r.GetField(), b.GetKey(), b.GetCount()))
}
return out
}
return out
return walk("", results)
}
// merged runs the service layer's fold over the answers of several spaces.
@@ -81,7 +87,8 @@ var _ = Describe("Merge and Finalize", func() {
opts := []*searchsvc.AggregationOption{
{Field: "audio.artist"},
metricOpt("audio.year", sum),
{Field: "audio.year", BucketDefinition: rangesOf(searchsvc.BucketSortBy_BUCKET_SORT_BY_KEY_AS_NUMBER, &searchsvc.BucketRange{To: "1980"}, &searchsvc.BucketRange{From: "1980"})},
{Field: "audio.year", BucketDefinition: rangesOf(searchsvc.BucketSortBy_BUCKET_SORT_BY_KEY_AS_NUMBER, &searchsvc.BucketRange{To: "1980"}, &searchsvc.BucketRange{From: "1980"}),
SubAggregations: []*searchsvc.AggregationOption{{Field: "audio.artist"}, metricOpt("audio.year", max)}},
}
got := merged(opts)
Expect(got).To(HaveLen(3))
@@ -89,7 +96,8 @@ var _ = Describe("Merge and Finalize", func() {
Expect(got[0].GetBuckets()).To(BeEmpty())
Expect(render(got)).To(Equal([]string{
"audio.year METRIC_KIND_SUM none",
"audio.year ..1980=0", "audio.year 1980..=0",
"audio.year ..1980=0", "audio.year ..1980=0 / audio.year METRIC_KIND_MAX none",
"audio.year 1980..=0", "audio.year 1980..=0 / audio.year METRIC_KIND_MAX none",
}))
})
@@ -146,6 +154,44 @@ var _ = Describe("Merge and Finalize", func() {
}))
})
It("keeps sibling metrics on one field apart below a bucket that spans spaces", func() {
opts := []*searchsvc.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{
metricOpt("audio.year", sum),
metricOpt("audio.year", avg),
}}}
space := func(years ...float64) []*searchsvc.AggregationResult {
return []*searchsvc.AggregationResult{buckets("audio.artist",
bucket("Saxon", int64(len(years)), metric("audio.year", sum, years...), metric("audio.year", avg, years...)),
)}
}
got := merged(opts, space(1971, 1975), space(1980))
Expect(render(got)).To(Equal([]string{
"audio.artist Saxon=3",
"audio.artist Saxon=3 / audio.year METRIC_KIND_SUM=5926",
"audio.artist Saxon=3 / audio.year METRIC_KIND_AVG=1975.3333333333333",
}))
})
It("answers nested results in request order", func() {
opts := []*searchsvc.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.genre"}, {Field: "audio.album"}, metricOpt("audio.year", max), {Field: "audio.composers"},
}}}
space := []*searchsvc.AggregationResult{buckets("audio.artist", bucket("Saxon", 1,
buckets("audio.genre", bucket("Metal", 1)),
buckets("audio.album", bucket("Wheels of Steel", 1)),
metric("audio.year", max, 1980),
buckets("audio.composers", bucket("Byford", 1)),
))}
for range 20 {
got := merged(opts, space, space)
fields := []string{}
for _, sub := range got[0].GetBuckets()[0].GetSubAggregations() {
fields = append(fields, sub.GetField())
}
Expect(fields).To(Equal([]string{"audio.genre", "audio.album", "audio.year", "audio.composers"}))
}
})
DescribeTable("reduces a metric over the values of all spaces",
func(kind searchsvc.MetricKind, want string) {
opts := []*searchsvc.AggregationOption{metricOpt("audio.year", kind)}
@@ -237,5 +283,21 @@ var _ = Describe("Merge and Finalize", func() {
)
Expect(render(got)).To(Equal([]string{"audio.artist Saxon=2"}))
})
It("shapes the buckets of a sub-aggregation by its own definition", func() {
opts := []*searchsvc.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{{
Field: "audio.album", Size: 2,
BucketDefinition: &searchsvc.BucketDefinition{SortBy: searchsvc.BucketSortBy_BUCKET_SORT_BY_KEY_AS_STRING, MinimumCount: 2},
}}}}
space := []*searchsvc.AggregationResult{buckets("audio.artist", bucket("Saxon", 4, buckets("audio.album",
bucket("Wheels of Steel", 1), bucket("Strong Arm of the Law", 1), bucket("Denim and Leather", 1), bucket("Crusader", 1),
)))}
Expect(render(merged(opts, space, space))).To(Equal([]string{
"audio.artist Saxon=8",
"audio.artist Saxon=8 / audio.album Crusader=2",
"audio.artist Saxon=8 / audio.album Denim and Leather=2",
}))
Expect(render(merged(opts, space))).To(Equal([]string{"audio.artist Saxon=4"}), "one space alone stays below the minimum count")
})
})
})
@@ -21,6 +21,9 @@ func ValidateOptions(opts []*searchsvc.AggregationOption, fieldType func(string)
if t != mapping.TypeNumeric {
return fmt.Errorf("metric aggregation needs a numeric field, %q is none", field)
}
if len(opt.GetSubAggregations()) > 0 {
return fmt.Errorf("metric aggregation on %q has no buckets to nest sub-aggregations in", field)
}
case KindRange:
parsed, err := ParseRanges(field, opt.GetBucketDefinition().GetRanges())
if err != nil {
@@ -34,6 +37,9 @@ func ValidateOptions(opts []*searchsvc.AggregationOption, fieldType func(string)
return err
}
}
if err := ValidateOptions(opt.GetSubAggregations(), fieldType); err != nil {
return err
}
}
return nil
}
@@ -39,6 +39,11 @@ var _ = Describe("ValidateOptions", func() {
Entry("numeric ranges on a number", &searchsvc.AggregationOption{Field: "audio.year", BucketDefinition: numeric}),
Entry("date ranges on a date", &searchsvc.AggregationOption{Field: "photo.takenDateTime", BucketDefinition: dates}),
Entry("a metric on a number", &searchsvc.AggregationOption{Field: "audio.year", MetricDefinition: sum}),
Entry("a tree of them", &searchsvc.AggregationOption{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.year", BucketDefinition: numeric, SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.album"}, {Field: "audio.year", MetricDefinition: sum},
}},
}}),
)
DescribeTable("rejects an aggregation the index cannot answer",
@@ -53,8 +58,13 @@ var _ = Describe("ValidateOptions", func() {
Entry("terms on a geopoint", &searchsvc.AggregationOption{Field: "location"}),
Entry("a metric on a keyword", &searchsvc.AggregationOption{Field: "audio.artist", MetricDefinition: sum}),
Entry("a metric on a date", &searchsvc.AggregationOption{Field: "photo.takenDateTime", MetricDefinition: sum}),
Entry("a metric with sub-aggregations", &searchsvc.AggregationOption{Field: "audio.year", MetricDefinition: sum,
SubAggregations: []*searchsvc.AggregationOption{{Field: "audio.artist"}}}),
Entry("numeric ranges on a keyword", &searchsvc.AggregationOption{Field: "audio.artist", BucketDefinition: numeric}),
Entry("numeric ranges on a date", &searchsvc.AggregationOption{Field: "photo.takenDateTime", BucketDefinition: numeric}),
Entry("date ranges on a number", &searchsvc.AggregationOption{Field: "audio.year", BucketDefinition: dates}),
Entry("an invalid aggregation two levels down", &searchsvc.AggregationOption{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.album", SubAggregations: []*searchsvc.AggregationOption{{Field: "photo.takenDateTime"}}},
}}),
)
})
+39 -5
View File
@@ -17,8 +17,8 @@ import (
)
// Bleve facets count the indexed terms of one field (numbers among them as
// prefix-coded terms), so every aggregation is folded from doc values by
// aggCollector, hooked into the collector walk through bleve's
// prefix-coded terms) and cannot nest, so every aggregation is folded from doc
// values by aggCollector, hooked into the collector walk through bleve's
// document-match-handler context key. Loading hits for them instead costs a
// stored-document decode per match.
@@ -39,6 +39,7 @@ type aggLevel struct {
kind aggregation.Kind
fieldType string
ranges aggregation.Ranges
children []*aggLevel
}
// numeric values are prefix-coded in the index, their terms come from the
@@ -56,6 +57,13 @@ func newAggLevel(opt *searchService.AggregationOption) (*aggLevel, error) {
}
l.ranges = ranges
}
for _, sub := range opt.GetSubAggregations() {
child, err := newAggLevel(sub)
if err != nil {
return nil, err
}
l.children = append(l.children, child)
}
return l, nil
}
@@ -69,6 +77,7 @@ type fieldValues struct {
type bucketAcc struct {
counts map[string]int64
subs map[string][]*bucketAcc
metric *searchService.Metric
}
@@ -76,7 +85,11 @@ func newBucketAcc(l *aggLevel) *bucketAcc {
if l.kind == aggregation.KindMetric {
return &bucketAcc{metric: &searchService.Metric{Kind: l.opt.GetMetricDefinition().GetKind()}}
}
return &bucketAcc{counts: map[string]int64{}}
a := &bucketAcc{counts: map[string]int64{}}
if len(l.children) > 0 {
a.subs = map[string][]*bucketAcc{}
}
return a
}
// aggCollector serves one search; bleve's collector is single-threaded.
@@ -119,6 +132,9 @@ func (c *aggCollector) register(l *aggLevel) {
} else {
fv.asNumbers = true
}
for _, child := range l.children {
c.register(child)
}
}
// The handler runs for every match, before the top-n cut.
@@ -216,8 +232,22 @@ func (c *aggCollector) fold(a *bucketAcc, l *aggLevel) {
}
}
func (c *aggCollector) foldBucket(a *bucketAcc, _ *aggLevel, key string) {
func (c *aggCollector) foldBucket(a *bucketAcc, l *aggLevel, key string) {
a.counts[key]++
if len(l.children) == 0 {
return
}
subs, ok := a.subs[key]
if !ok {
subs = make([]*bucketAcc, len(l.children))
for i, child := range l.children {
subs[i] = newBucketAcc(child)
}
a.subs[key] = subs
}
for i, child := range l.children {
c.fold(subs[i], child)
}
}
// results returns one result per aggregation, in request order.
@@ -238,7 +268,11 @@ func (a *bucketAcc) result(l *aggLevel) *searchService.AggregationResult {
counted := make(map[string]*searchService.Bucket, len(a.counts))
for key, count := range a.counts {
counted[key] = &searchService.Bucket{Key: key, Count: count}
b := &searchService.Bucket{Key: key, Count: count}
for i, child := range l.children {
b.SubAggregations = append(b.SubAggregations, a.subs[key][i].result(child))
}
counted[key] = b
}
if l.kind == aggregation.KindRange {
r.Buckets = aggregation.RangeBuckets(l.opt, counted)
@@ -12,10 +12,10 @@ import (
"github.com/opencloud-eu/opencloud/services/search/pkg/aggregation"
)
// Build renders the aggregations as native OpenSearch aggregations, each
// named by its position. A terms aggregation asks for every bucket up to the
// limit: the service layer cuts to the requested size after the cross-space
// merge, a cut here would make that merge inexact.
// Build renders the aggregation tree as native OpenSearch aggregations, each
// named by its position among its siblings. A terms aggregation asks for
// every bucket up to the limit: the service layer cuts to the requested size
// after the cross-space merge, a cut here would make that merge inexact.
func Build(opts []*searchsvc.AggregationOption) (map[string]any, error) {
if len(opts) == 0 {
return nil, nil
@@ -51,6 +51,13 @@ func build(opt *searchsvc.AggregationOption) (map[string]any, error) {
default:
entry = map[string]any{"terms": map[string]any{"field": opt.GetField(), "size": aggregation.MaxBuckets}}
}
children, err := Build(opt.GetSubAggregations())
if err != nil {
return nil, err
}
if children != nil {
entry["aggs"] = children
}
return entry, nil
}
@@ -159,10 +166,18 @@ func parseBucket(opt *searchsvc.AggregationOption, raw json.RawMessage) (*search
KeyAsString *string `json:"key_as_string"`
DocCount int64 `json:"doc_count"`
}
children := aggNode{}
if err := json.Unmarshal(raw, &head); err != nil {
return nil, fmt.Errorf("decode bucket of %s: %w", opt.GetField(), err)
}
return &searchsvc.Bucket{Key: bucketKey(head.Key, head.KeyAsString), Count: head.DocCount}, nil
if err := json.Unmarshal(raw, &children); err != nil {
return nil, fmt.Errorf("decode bucket of %s: %w", opt.GetField(), err)
}
subs, err := parse(opt.GetSubAggregations(), children)
if err != nil {
return nil, err
}
return &searchsvc.Bucket{Key: bucketKey(head.Key, head.KeyAsString), Count: head.DocCount, SubAggregations: subs}, nil
}
// bucketKey spells a response key like the bleve backend does: a number
@@ -110,6 +110,11 @@ var _ = Describe("Aggregations", func() {
Expect(err).To(HaveOccurred())
})
It("rejects a malformed range below another aggregation", func() {
_, err := aggs.Build([]*searchsvc.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchsvc.AggregationOption{malformed}}})
Expect(err).To(HaveOccurred())
})
It("lists every requested range in request order, empty ones included", func() {
out := parse(`{"a_0": {"buckets": [
{"key": "1970..1980", "doc_count": 4},
@@ -177,4 +182,58 @@ var _ = Describe("Aggregations", func() {
&searchsvc.AggregationOption{Field: "audio.year", MetricDefinition: &searchsvc.MetricDefinition{Kind: searchsvc.MetricKind_METRIC_KIND_SUM}},
`{"a_0": {"count": "many"}}`),
)
It("nests sub-aggregations in their parent, named by position", func() {
artist := build(&searchsvc.AggregationOption{
Field: "audio.artist",
SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.year", BucketDefinition: &searchsvc.BucketDefinition{Ranges: []*searchsvc.BucketRange{{To: "1980"}}},
SubAggregations: []*searchsvc.AggregationOption{{Field: "audio.album"}}},
{Field: "audio.duration", MetricDefinition: &searchsvc.MetricDefinition{Kind: searchsvc.MetricKind_METRIC_KIND_SUM}},
},
})["a_0"].(map[string]any)
children := artist["aggs"].(map[string]any)
Expect(children).To(HaveKey("a_1"), "the metric")
Expect(children["a_1"].(map[string]any)).To(HaveKey("stats"))
years := children["a_0"].(map[string]any)
Expect(years).To(HaveKey("range"))
albums := years["aggs"].(map[string]any)["a_0"].(map[string]any)["terms"].(map[string]any)
Expect(albums["field"]).To(Equal("audio.album"))
})
It("reads nested results below their bucket, terms below a range below terms too", func() {
out := parse(`{"a_0": {"buckets": [
{"key": "Saxon", "doc_count": 2,
"a_0": {"buckets": [
{"key": "..1980", "doc_count": 2, "a_0": {"buckets": [{"key": "Wheels of Steel", "doc_count": 2}]}},
{"key": "1980..", "doc_count": 0, "a_0": {"buckets": []}}
]},
"a_1": {"count": 2, "min": 1971.0, "max": 1975.0, "sum": 3946.0}},
{"key": "Accept", "doc_count": 1,
"a_0": {"buckets": [
{"key": "..1980", "doc_count": 0, "a_0": {"buckets": []}},
{"key": "1980..", "doc_count": 1, "a_0": {"buckets": [{"key": "Breaker", "doc_count": 1}]}}
]},
"a_1": {"count": 1, "min": 1981.0, "max": 1981.0, "sum": 1981.0}}
]}}`, &searchsvc.AggregationOption{
Field: "audio.artist",
SubAggregations: []*searchsvc.AggregationOption{
{Field: "audio.year", BucketDefinition: &searchsvc.BucketDefinition{Ranges: []*searchsvc.BucketRange{{To: "1980"}, {From: "1980"}}},
SubAggregations: []*searchsvc.AggregationOption{{Field: "audio.album"}}},
{Field: "audio.year", MetricDefinition: &searchsvc.MetricDefinition{Kind: searchsvc.MetricKind_METRIC_KIND_SUM}},
},
})
Expect(keys(out[0])).To(Equal([]string{"Saxon=2", "Accept=1"}))
saxon := out[0].GetBuckets()[0].GetSubAggregations()
Expect(saxon).To(HaveLen(2))
Expect(keys(saxon[0])).To(Equal([]string{"..1980=2", "1980..=0"}))
Expect(keys(saxon[0].GetBuckets()[0].GetSubAggregations()[0])).To(Equal([]string{"Wheels of Steel=2"}), "only the albums of this artist")
Expect(saxon[0].GetBuckets()[1].GetSubAggregations()[0].GetBuckets()).To(BeEmpty())
Expect(saxon[1].GetMetric().GetSum()).To(Equal(3946.0))
accept := out[0].GetBuckets()[1].GetSubAggregations()
Expect(keys(accept[0].GetBuckets()[1].GetSubAggregations()[0])).To(Equal([]string{"Breaker=1"}))
})
})
+10
View File
@@ -745,11 +745,20 @@ Fixtures:
| AGG-07 | `mediatype:image` reads `photo.takenDateTime buckets per date range, an empty range counts zero` | photo.take...00:00Z=0, photo.take...00:00Z=1, photo.take...00:00Z=2, photo.take...00:00Z=2 | photo.take...00:00Z=0, photo.take...00:00Z=1, photo.take...00:00Z=2, photo.take...00:00Z=2 | photo.take...00:00Z=0, photo.take...00:00Z=1, photo.take...00:00Z=2, photo.take...00:00Z=2 | ✅ |
| AGG-08 | `mediatype:image` reads `open-ended date ranges` | photo.take...00:00Z=3, photo.take...:00Z..=1 | photo.take...00:00Z=3, photo.take...:00Z..=1 | photo.take...00:00Z=3, photo.take...:00Z..=1 | ✅ |
| AGG-09 | `mediatype:image` reads `malformed date range bound` | bad request | bad request | bad request | ✅ |
| AGG-10 | `mediatype:audio` reads `album buckets nested in artist buckets` | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2 | ✅ |
| AGG-11 | `mediatype:audio` reads `sum and avg of audio.year per artist` | audio.arti... count=2, audio.arti... count=3, audio.arti...sum=3946, audio.arti...sum=5982, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... count=2, audio.arti... count=3, audio.arti...sum=3946, audio.arti...sum=5982, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... count=2, audio.arti... count=3, audio.arti...sum=3946, audio.arti...sum=5982, audio.artist Motörhead=3, audio.artist Saxon=2 | ✅ |
| AGG-12 | `mediatype:audio` reads `artist buckets nested in audio.year decades` | audio.year 1970..1980=2, audio.year 1980..1990=1, audio.year 1990..2000=1, audio.year 2000..2010=3, audio.year... Saxon=2, audio.year...örhead=1, audio.year...örhead=1, audio.year...örhead=1 | audio.year 1970..1980=2, audio.year 1980..1990=1, audio.year 1990..2000=1, audio.year 2000..2010=3, audio.year... Saxon=2, audio.year...örhead=1, audio.year...örhead=1, audio.year...örhead=1 | audio.year 1970..1980=2, audio.year 1980..1990=1, audio.year 1990..2000=1, audio.year 2000..2010=3, audio.year... Saxon=2, audio.year...örhead=1, audio.year...örhead=1, audio.year...örhead=1 | ✅ |
| AGG-13 | `mediatype:audio` reads `max audio.year per album per artist, three levels` | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.arti...max=1975, audio.arti...max=1999, audio.arti...max=2001, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.arti...max=1975, audio.arti...max=1999, audio.arti...max=2001, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.arti...max=1975, audio.arti...max=1999, audio.arti...max=2001, audio.artist Motörhead=3, audio.artist Saxon=2 | ✅ |
| AGG-14 | `mediatype:image` reads `MimeType buckets nested in open-ended date ranges` | photo.take...00:00Z=3, photo.take...:00Z..=1, photo.take...e/jpeg=1, photo.take...e/jpeg=3 | photo.take...00:00Z=3, photo.take...:00Z..=1, photo.take...e/jpeg=1, photo.take...e/jpeg=3 | error | ❌ known |
| AGG-15 | `mediatype:audio` reads `nested aggregations cover every match on a page of one` | 1 of 7 matches, audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2, audio.year sum=13942 | 1 of 7 matches, audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2, audio.year sum=13942 | 1 of 7 matches, audio.arti... Steel=2, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, audio.artist Saxon=2, audio.year sum=13942 | ✅ |
| AGG-18 | `mediatype:audio` reads `two range aggregations and a metric on audio.year stay apart` | audio.year ..1980=2, audio.year ..2000=4, audio.year 1980..=5, audio.year 2000..=3, audio.year max=2009 | audio.year ..1980=2, audio.year ..2000=4, audio.year 1980..=5, audio.year 2000..=3, audio.year max=2009 | audio.year ..1980=2, audio.year ..2000=4, audio.year 1980..=5, audio.year 2000..=3, audio.year max=2009 | ✅ |
| AGG-20 | `mediatype:audio` reads `album buckets in audio.year ranges in artist buckets` | audio.arti... Steel=2, audio.arti.....1980=0, audio.arti.....1980=2, audio.arti...1980..=0, audio.arti...1980..=3, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, … +1 more | audio.arti... Steel=2, audio.arti.....1980=0, audio.arti.....1980=2, audio.arti...1980..=0, audio.arti...1980..=3, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, … +1 more | audio.arti... Steel=2, audio.arti.....1980=0, audio.arti.....1980=2, audio.arti...1980..=0, audio.arti...1980..=3, audio.arti...Bomber=2, audio.arti...Spades=1, audio.artist Motörhead=3, … +1 more | ✅ |
| AGG-21 | `mediatype:image` reads `a metric without a single value has none` | audio.year avg none, audio.year min none, audio.year sum none | audio.year avg none, audio.year min none, audio.year sum none | audio.year avg none, audio.year min none, audio.year sum none | ✅ |
| AGG-34 | `mediatype:audio` reads `term buckets on the numeric audio.year` | audio.year 1971=1, audio.year 1975=1, audio.year 1982=1, audio.year 1999=1, audio.year 2001=1, audio.year 2005=1, audio.year 2009=1 | audio.year 1971=1, audio.year 1975=1, audio.year 1982=1, audio.year 1999=1, audio.year 2001=1, audio.year 2005=1, audio.year 2009=1 | audio.year 1971=1, audio.year 1975=1, audio.year 1982=1, audio.year 1999=1, audio.year 2001=1, audio.year 2005=1, audio.year 2009=1 | ✅ |
| AGG-35 | `mediatype:audio` reads `audio.year buckets nested in artist buckets` | audio.arti...r 1971=1, audio.arti...r 1975=1, audio.arti...r 1982=1, audio.arti...r 1999=1, audio.arti...r 2001=1, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti...r 1971=1, audio.arti...r 1975=1, audio.arti...r 1982=1, audio.arti...r 1999=1, audio.arti...r 2001=1, audio.artist Motörhead=3, audio.artist Saxon=2 | audio.arti...r 1971=1, audio.arti...r 1975=1, audio.arti...r 1982=1, audio.arti...r 1999=1, audio.arti...r 2001=1, audio.artist Motörhead=3, audio.artist Saxon=2 | ✅ |
| AGG-37 | `mediatype:audio` reads `term buckets on the bool audio.hasDrm, spelled true and false` | audio.hasDrm false=1, audio.hasDrm true=1 | audio.hasDrm false=1, audio.hasDrm true=1 | audio.hasDrm false=1, audio.hasDrm true=1 | ✅ |
| AGG-39 | `mediatype:audio` reads `no bucket for the empty Title of every match` | no match | no match | no match | ✅ |
| AGG-40 | `mediatype:audio` reads `no bucket for the empty Title, with sub-aggregations neither` | no match | no match | no match | ✅ |
| AGG-41 | `mediatype:audio` reads `matches of the same score in the order of their ids, page after page` | 3 of 7 matches, a.mp3, b.mp3, c.mp3 | 3 of 7 matches, a.mp3, b.mp3, c.mp3 | 3 of 7 matches, a.mp3, b.mp3, c.mp3 | ✅ |
### cardinality
@@ -773,3 +782,4 @@ Fixtures:
| AGG-17 | `mediatype:audio` reads `one bucket per artist, cardinality above the page size` | 10050 items | audio.arti...-00000=1, audio.arti...-00001=1, audio.arti...-00002=1, audio.arti...-00003=1, audio.arti...-00004=1, audio.arti...-00005=1, audio.arti...-00006=1, audio.arti...-00007=1, … +10042 more | audio.arti...-00000=1, audio.arti...-00001=1, audio.arti...-00002=1, audio.arti...-00003=1, audio.arti...-00004=1, audio.arti...-00005=1, audio.arti...-00006=1, audio.arti...-00007=1, … +10042 more | ✅ |
| AGG-42 | `mediatype:audio` reads `the total counts every match` | 1 of 10050 matches | 1 of 10050 matches | 1 of 10050 matches | ✅ |
| AGG-43 | `mediatype:audio` reads `a page reaching beyond the first 10000 matches is refused` | bad request | bad request | bad request | ✅ |
| AGG-44 | `mediatype:audio` reads `more than 65535 buckets in one request are refused` | bad request | bad request | bad request | ✅ |
+106 -2
View File
@@ -159,6 +159,70 @@ func aggregationCases() []aggCase {
&searchService.BucketRange{From: "2018-08-11T00:00:00Z", To: "not-a-date"},
)}},
wantBadRequest: true},
{id: 10, query: "mediatype:audio", reads: "album buckets nested in artist buckets",
aggs: []*searchService.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{{Field: "audio.album"}}}},
want: []string{
"audio.artist Saxon=2", "audio.artist Saxon=2 / audio.album Wheels of Steel=2",
"audio.artist Motörhead=3", "audio.artist Motörhead=3 / audio.album Bomber=2", "audio.artist Motörhead=3 / audio.album Ace of Spades=1",
}},
{id: 11, query: "mediatype:audio", reads: "sum and avg of audio.year per artist",
aggs: []*searchService.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{
metric("audio.year", searchService.MetricKind_METRIC_KIND_SUM),
metric("audio.year", searchService.MetricKind_METRIC_KIND_AVG),
}}},
want: []string{
"audio.artist Saxon=2", "audio.artist Saxon=2 / audio.year sum=3946", "audio.artist Saxon=2 / audio.year avg sum=3946 count=2",
"audio.artist Motörhead=3", "audio.artist Motörhead=3 / audio.year sum=5982", "audio.artist Motörhead=3 / audio.year avg sum=5982 count=3",
}},
{id: 12, query: "mediatype:audio", reads: "artist buckets nested in audio.year decades",
aggs: []*searchService.AggregationOption{{Field: "audio.year", BucketDefinition: ranges(
&searchService.BucketRange{From: "1970", To: "1980"},
&searchService.BucketRange{From: "1980", To: "1990"},
&searchService.BucketRange{From: "1990", To: "2000"},
&searchService.BucketRange{From: "2000", To: "2010"},
), SubAggregations: []*searchService.AggregationOption{{Field: "audio.artist"}}}},
want: []string{
"audio.year 1970..1980=2", "audio.year 1970..1980=2 / audio.artist Saxon=2",
"audio.year 1980..1990=1", "audio.year 1980..1990=1 / audio.artist Motörhead=1",
"audio.year 1990..2000=1", "audio.year 1990..2000=1 / audio.artist Motörhead=1",
"audio.year 2000..2010=3", "audio.year 2000..2010=3 / audio.artist Motörhead=1",
}},
{id: 13, query: "mediatype:audio", reads: "max audio.year per album per artist, three levels",
aggs: []*searchService.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{
{Field: "audio.album", SubAggregations: []*searchService.AggregationOption{
metric("audio.year", searchService.MetricKind_METRIC_KIND_MAX),
}},
}}},
want: []string{
"audio.artist Saxon=2", "audio.artist Saxon=2 / audio.album Wheels of Steel=2", "audio.artist Saxon=2 / audio.album Wheels of Steel=2 / audio.year max=1975",
"audio.artist Motörhead=3", "audio.artist Motörhead=3 / audio.album Bomber=2", "audio.artist Motörhead=3 / audio.album Bomber=2 / audio.year max=1999",
"audio.artist Motörhead=3 / audio.album Ace of Spades=1", "audio.artist Motörhead=3 / audio.album Ace of Spades=1 / audio.year max=2001",
}},
{id: 14, query: "mediatype:image", reads: "MimeType buckets nested in open-ended date ranges",
aggs: []*searchService.AggregationOption{{Field: "photo.takenDateTime", BucketDefinition: ranges(
&searchService.BucketRange{To: "2019-01-01T00:00:00Z"},
&searchService.BucketRange{From: "2019-01-01T00:00:00Z"},
), SubAggregations: []*searchService.AggregationOption{{Field: "MimeType"}}}},
want: []string{
"photo.takenDateTime ..2019-01-01T00:00:00Z=3", "photo.takenDateTime ..2019-01-01T00:00:00Z=3 / MimeType image/jpeg=3",
"photo.takenDateTime 2019-01-01T00:00:00Z..=1", "photo.takenDateTime 2019-01-01T00:00:00Z..=1 / MimeType image/jpeg=1",
},
// OpenSearch maps MimeType as wildcard, which serves no doc values to
// aggregate on; the follow-up is an aggregatable keyword sibling
engineOverrides: map[string]override{"opensearch": {want: []string{"error"}}}},
{id: 15, query: "mediatype:audio", reads: "nested aggregations cover every match on a page of one",
pageSize: 1,
aggs: []*searchService.AggregationOption{
{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{{Field: "audio.album"}}},
metric("audio.year", searchService.MetricKind_METRIC_KIND_SUM),
},
want: []string{
"1 of 7 matches",
"audio.artist Saxon=2", "audio.artist Saxon=2 / audio.album Wheels of Steel=2",
"audio.artist Motörhead=3", "audio.artist Motörhead=3 / audio.album Bomber=2", "audio.artist Motörhead=3 / audio.album Ace of Spades=1",
"audio.year sum=13942",
}},
// 16 and 19 pinned malformed bounds twice over, 17 is the cardinality case below
{id: 18, query: "mediatype:audio", reads: "two range aggregations and a metric on audio.year stay apart",
aggs: []*searchService.AggregationOption{
{Field: "audio.year", BucketDefinition: ranges(&searchService.BucketRange{To: "1980"}, &searchService.BucketRange{From: "1980"})},
@@ -170,6 +234,21 @@ func aggregationCases() []aggCase {
"audio.year ..2000=4", "audio.year 2000..=3",
"audio.year max=2009",
}},
{id: 20, query: "mediatype:audio", reads: "album buckets in audio.year ranges in artist buckets",
aggs: []*searchService.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{{
Field: "audio.year",
BucketDefinition: ranges(&searchService.BucketRange{To: "1980"}, &searchService.BucketRange{From: "1980"}),
SubAggregations: []*searchService.AggregationOption{{Field: "audio.album"}},
}}}},
want: []string{
"audio.artist Saxon=2",
"audio.artist Saxon=2 / audio.year ..1980=2", "audio.artist Saxon=2 / audio.year ..1980=2 / audio.album Wheels of Steel=2",
"audio.artist Saxon=2 / audio.year 1980..=0",
"audio.artist Motörhead=3",
"audio.artist Motörhead=3 / audio.year ..1980=0",
"audio.artist Motörhead=3 / audio.year 1980..=3",
"audio.artist Motörhead=3 / audio.year 1980..=3 / audio.album Bomber=2", "audio.artist Motörhead=3 / audio.year 1980..=3 / audio.album Ace of Spades=1",
}},
{id: 21, query: "mediatype:image", reads: "a metric without a single value has none",
aggs: []*searchService.AggregationOption{
metric("audio.year", searchService.MetricKind_METRIC_KIND_SUM),
@@ -183,11 +262,20 @@ func aggregationCases() []aggCase {
"audio.year 1971=1", "audio.year 1975=1", "audio.year 1982=1", "audio.year 1999=1",
"audio.year 2001=1", "audio.year 2005=1", "audio.year 2009=1",
}},
{id: 35, query: "mediatype:audio", reads: "audio.year buckets nested in artist buckets",
aggs: []*searchService.AggregationOption{{Field: "audio.artist", SubAggregations: []*searchService.AggregationOption{{Field: "audio.year"}}}},
want: []string{
"audio.artist Saxon=2", "audio.artist Saxon=2 / audio.year 1971=1", "audio.artist Saxon=2 / audio.year 1975=1",
"audio.artist Motörhead=3", "audio.artist Motörhead=3 / audio.year 1982=1",
"audio.artist Motörhead=3 / audio.year 1999=1", "audio.artist Motörhead=3 / audio.year 2001=1",
}},
{id: 37, query: "mediatype:audio", reads: "term buckets on the bool audio.hasDrm, spelled true and false",
aggs: []*searchService.AggregationOption{{Field: "audio.hasDrm"}},
want: []string{"audio.hasDrm true=1", "audio.hasDrm false=1"}},
{id: 39, query: "mediatype:audio", reads: "no bucket for the empty Title of every match",
aggs: []*searchService.AggregationOption{{Field: "Title"}}},
{id: 40, query: "mediatype:audio", reads: "no bucket for the empty Title, with sub-aggregations neither",
aggs: []*searchService.AggregationOption{{Field: "Title", SubAggregations: []*searchService.AggregationOption{{Field: "audio.artist"}}}}},
{id: 41, query: "mediatype:audio", reads: "matches of the same score in the order of their ids, page after page",
pageSize: 3, listMatches: true,
want: []string{"3 of 7 matches", "a.mp3", "b.mp3", "c.mp3"}},
@@ -195,7 +283,7 @@ func aggregationCases() []aggCase {
}
// renderAggregations flattens an answer into comparable strings, one per
// bucket or metric.
// bucket or metric. A nested result renders under its bucket, joined by " / ".
func renderAggregations(resp *searchService.SearchIndexResponse, err error) []string {
if err != nil {
if _, ok := err.(errtypes.BadRequest); ok {
@@ -227,7 +315,11 @@ func renderAggregation(prefix string, a *searchService.AggregationResult) []stri
out := []string{}
for _, b := range a.Buckets {
out = append(out, prefix+fmt.Sprintf("%s %s=%d", a.Field, b.Key, b.Count))
line := prefix + fmt.Sprintf("%s %s=%d", a.Field, b.Key, b.Count)
out = append(out, line)
for _, sub := range b.SubAggregations {
out = append(out, renderAggregation(line+" / ", sub)...)
}
}
return out
@@ -243,6 +335,15 @@ func cardinalityFixtures() []search.Resource {
return docs
}
// nested puts a terms aggregation on each field into the one before it.
func nested(fields ...string) *searchService.AggregationOption {
opt := &searchService.AggregationOption{Field: fields[0]}
if len(fields) > 1 {
opt.SubAggregations = []*searchService.AggregationOption{nested(fields[1:]...)}
}
return opt
}
func cardinalityCases() []aggCase {
count := 10050
return []aggCase{
@@ -255,6 +356,9 @@ func cardinalityCases() []aggCase {
{id: 43, query: "mediatype:audio", reads: "a page reaching beyond the first 10000 matches is refused",
pageSize: 10001,
wantBadRequest: true},
{id: 44, query: "mediatype:audio", reads: "more than 65535 buckets in one request are refused",
aggs: []*searchService.AggregationOption{nested("audio.artist", "Name", "ID", "RootID", "ParentID", "audio.album", "audio.year")},
wantBadRequest: true},
}
}