diff --git a/pkg/eventrepo/event_repo_test.go b/pkg/eventrepo/event_repo_test.go index 003c7a0..18f16fa 100644 --- a/pkg/eventrepo/event_repo_test.go +++ b/pkg/eventrepo/event_repo_test.go @@ -547,7 +547,6 @@ func TestListIndexesAdvanced(t *testing.T) { Tags: []string{"vehicle", "telemetry", "status"}, } - // Add an event with different tags for testing ArrayFilterOption eventIdx4 := &cloudevent.CloudEventHeader{ ID: "event-source3-producer4", Subject: subject1, @@ -577,10 +576,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by single type status events", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeStatus}, + In: []string{cloudevent.TypeStatus}, }, }, expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, @@ -589,10 +588,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by multiple types with OR logic", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeStatus, cloudevent.TypeFingerprint}, + In: []string{cloudevent.TypeStatus, cloudevent.TypeFingerprint}, }, }, expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeFingerprintSource2Producer2, keyTypeStatusSource1Producer1}, @@ -601,11 +600,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by type with negation", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeFingerprint}, - Negate: true, + NotIn: []string{cloudevent.TypeFingerprint}, }, }, expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, @@ -614,10 +612,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by source: multiple results", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Source: &grpc.StringFilterOption{ - HasAny: []string{"source-1"}, + In: []string{"source-1"}, }, }, expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, @@ -626,13 +624,13 @@ func TestListIndexesAdvanced(t *testing.T) { name: "combine multiple filters (AND logic)", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeStatus}, + In: []string{cloudevent.TypeStatus}, }, Source: &grpc.StringFilterOption{ - HasAny: []string{"source-1"}, + In: []string{"source-1"}, }, }, expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, @@ -641,13 +639,12 @@ func TestListIndexesAdvanced(t *testing.T) { name: "OR logic within StringFilterOption", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeAttestation}, - Or: &grpc.StringFilterOption{ - HasAny: []string{cloudevent.TypeStatus}, - Negate: true, + In: []string{cloudevent.TypeAttestation}, + Or: []*grpc.StringFilterOption{ + {NotIn: []string{cloudevent.TypeStatus}}, }, }, }, @@ -657,10 +654,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "no matching records", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Type: &grpc.StringFilterOption{ - HasAny: []string{"non-existent-type"}, + In: []string{"non-existent-type"}, }, }, expectedIndexKeys: []string{}, @@ -670,10 +667,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by tags: has any (telemetry)", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAny: []string{"telemetry"}, + ContainsAny: []string{"telemetry"}, }, }, expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3}, @@ -682,10 +679,10 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by tags: has all (vehicle AND status)", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAll: []string{"vehicle", "status"}, + ContainsAll: []string{"vehicle", "status"}, }, }, expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, @@ -694,23 +691,25 @@ func TestListIndexesAdvanced(t *testing.T) { name: "filter by tags: has any with multiple values", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAny: []string{"security", "realtime"}, + ContainsAny: []string{"security", "realtime"}, + Or: []*grpc.ArrayFilterOption{ + {ContainsAll: []string{"vehicle", "status"}}, + }, }, }, - expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeFingerprintSource2Producer2}, + expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeStatusSource1Producer3, keyTypeFingerprintSource2Producer2, keyTypeStatusSource1Producer1}, }, { name: "filter by tags: negated has_any", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAny: []string{"vehicle"}, - Negate: true, + NotContainsAny: []string{"vehicle"}, }, }, expectedIndexKeys: []string{keyTypeStatusSource3Producer4, keyTypeFingerprintSource2Producer2}, @@ -719,12 +718,12 @@ func TestListIndexesAdvanced(t *testing.T) { name: "complex tags filter: has_any OR has_all combination", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAny: []string{"fingerprint"}, - Or: &grpc.ArrayFilterOption{ - HasAll: []string{"telemetry", "realtime"}, + ContainsAny: []string{"fingerprint"}, + Or: []*grpc.ArrayFilterOption{ + {ContainsAll: []string{"telemetry", "realtime"}}, }, }, }, @@ -734,12 +733,12 @@ func TestListIndexesAdvanced(t *testing.T) { name: "complex tags filter: has_all with OR chain", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAll: []string{"vehicle", "telemetry"}, - Or: &grpc.ArrayFilterOption{ - HasAny: []string{"security"}, + ContainsAll: []string{"vehicle", "telemetry"}, + Or: []*grpc.ArrayFilterOption{ + {ContainsAny: []string{"security"}}, }, }, }, @@ -749,23 +748,37 @@ func TestListIndexesAdvanced(t *testing.T) { name: "complex negated tags with multiple conditions", advancedOpts: &grpc.AdvancedSearchOptions{ Subject: &grpc.StringFilterOption{ - HasAny: []string{subject1}, + In: []string{subject1}, }, Tags: &grpc.ArrayFilterOption{ - HasAny: []string{"fingerprint", "telemetry"}, - Negate: true, // Does NOT have fingerprint or telemetry - Or: &grpc.ArrayFilterOption{ - HasAll: []string{"telemetry", "status"}, + NotContainsAny: []string{"fingerprint", "telemetry"}, + Or: []*grpc.ArrayFilterOption{ + {ContainsAll: []string{"telemetry", "status"}}, }, }, }, expectedIndexKeys: []string{keyTypeStatusSource1Producer3, keyTypeStatusSource1Producer1}, }, + { + name: "complex negated tags with or override", + advancedOpts: &grpc.AdvancedSearchOptions{ + Subject: &grpc.StringFilterOption{ + In: []string{subject1}, + }, + Tags: &grpc.ArrayFilterOption{ + ContainsAll: []string{"vehicle", "telemetry"}, + NotContainsAny: []string{"fingerprint", "telemetry", "security"}, + Or: []*grpc.ArrayFilterOption{ + {ContainsAll: []string{"telemetry", "status"}}, + }, + }, + }, + expectedIndexKeys: []string{keyTypeStatusSource1Producer3}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Parallel() results, err := indexService.ListIndexesAdvanced(t.Context(), 10, tt.advancedOpts) if tt.expectedError { require.Error(t, err, "Expected error but got none") diff --git a/pkg/eventrepo/eventrepo.go b/pkg/eventrepo/eventrepo.go index 879d10a..3372dca 100644 --- a/pkg/eventrepo/eventrepo.go +++ b/pkg/eventrepo/eventrepo.go @@ -322,37 +322,37 @@ func convertSearchOptionsToAdvanced(opts *grpc.SearchOptions) *grpc.AdvancedSear // Convert each field to StringFilterOption with has_any logic if opts.GetType() != nil { advanced.Type = &grpc.StringFilterOption{ - HasAny: []string{opts.GetType().GetValue()}, + In: []string{opts.GetType().GetValue()}, } } if opts.GetDataVersion() != nil { advanced.DataVersion = &grpc.StringFilterOption{ - HasAny: []string{opts.GetDataVersion().GetValue()}, + In: []string{opts.GetDataVersion().GetValue()}, } } if opts.GetSubject() != nil { advanced.Subject = &grpc.StringFilterOption{ - HasAny: []string{opts.GetSubject().GetValue()}, + In: []string{opts.GetSubject().GetValue()}, } } if opts.GetSource() != nil { advanced.Source = &grpc.StringFilterOption{ - HasAny: []string{opts.GetSource().GetValue()}, + In: []string{opts.GetSource().GetValue()}, } } if opts.GetProducer() != nil { advanced.Producer = &grpc.StringFilterOption{ - HasAny: []string{opts.GetProducer().GetValue()}, + In: []string{opts.GetProducer().GetValue()}, } } if opts.GetExtras() != nil { advanced.Extras = &grpc.StringFilterOption{ - HasAny: []string{opts.GetExtras().GetValue()}, + In: []string{opts.GetExtras().GetValue()}, } } if opts.GetId() != nil { advanced.Id = &grpc.StringFilterOption{ - HasAny: []string{opts.GetId().GetValue()}, + In: []string{opts.GetId().GetValue()}, } } @@ -375,86 +375,96 @@ func AdvancedSearchOptionsToQueryMod(opts *grpc.AdvancedSearchOptions) []qm.Quer // Handle advanced filtering for each field if opts.GetType() != nil { - mods = appendStringFilterMods(mods, opts.GetType(), chindexer.TypeColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetType(), chindexer.TypeColumn)...)) } if opts.GetDataVersion() != nil { - mods = appendStringFilterMods(mods, opts.GetDataVersion(), chindexer.DataVersionColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetDataVersion(), chindexer.DataVersionColumn)...)) } if opts.GetSubject() != nil { - mods = appendStringFilterMods(mods, opts.GetSubject(), chindexer.SubjectColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetSubject(), chindexer.SubjectColumn)...)) } if opts.GetSource() != nil { - mods = appendStringFilterMods(mods, opts.GetSource(), chindexer.SourceColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetSource(), chindexer.SourceColumn)...)) } if opts.GetProducer() != nil { - mods = appendStringFilterMods(mods, opts.GetProducer(), chindexer.ProducerColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetProducer(), chindexer.ProducerColumn)...)) } if opts.GetExtras() != nil { - mods = appendStringFilterMods(mods, opts.GetExtras(), chindexer.ExtrasColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetExtras(), chindexer.ExtrasColumn)...)) } if opts.GetId() != nil { - mods = appendStringFilterMods(mods, opts.GetId(), chindexer.IDColumn) + mods = append(mods, qm.Expr(stringFilterMods(opts.GetId(), chindexer.IDColumn)...)) } if opts.GetTags() != nil { - mods = appendArrayFilterMods(mods, opts.GetTags(), tagsColumn) + mods = append(mods, qm.Expr(arrayFilterMods(opts.GetTags(), tagsColumn)...)) } return mods } -// appendStringFilterMods converts a StringFilterOption to query modifications. -func appendStringFilterMods(mods []qm.QueryMod, filter *grpc.StringFilterOption, columnName string) []qm.QueryMod { +// stringFilterMods converts a StringFilterOption to query modifications. +func stringFilterMods(filter *grpc.StringFilterOption, columnName string) []qm.QueryMod { + var mods []qm.QueryMod if filter == nil { return nil } // Process has_any (OR logic) - if len(filter.GetHasAny()) > 0 { - if filter.GetNegate() { - mods = append(mods, qm.Where(columnName+" NOT IN ?", filter.GetHasAny())) - } else { - mods = append(mods, qm.WhereIn(columnName+" IN ?", filter.GetHasAny())) - } + if len(filter.GetIn()) > 0 { + mods = append(mods, qm.Where(columnName+" IN (?)", filter.GetIn())) + } + if len(filter.GetNotIn()) > 0 { + mods = append(mods, qm.Where(columnName+" NOT IN (?)", filter.GetNotIn())) } - if filter.GetOr() != nil { - orMods := appendStringFilterMods(nil, filter.GetOr(), columnName) - if len(orMods) > 0 { - mods = append(mods, qm.Or2(qm.Expr(orMods...))) + for _, cond := range filter.GetOr() { + clauseMods := stringFilterMods(cond, columnName) + if len(clauseMods) != 0 { + mods = append(mods, qm.Or2(qm.Expr(clauseMods...))) } } + + if len(filter.GetOr()) != 0 { + mods = []qm.QueryMod{qm.Expr(mods...)} + } return mods } -// appendArrayFilterMods converts an ArrayFilterOption to query modifications. -func appendArrayFilterMods(mods []qm.QueryMod, filter *grpc.ArrayFilterOption, columnName string) []qm.QueryMod { +// arrayFilterMods converts an ArrayFilterOption to query modifications. +func arrayFilterMods(filter *grpc.ArrayFilterOption, columnName string) []qm.QueryMod { + var mods []qm.QueryMod if filter == nil { return mods } - var clause string - if filter.GetNegate() { - clause = "NOT " - } - if len(filter.GetHasAny()) > 0 { - mods = append(mods, qm.Where(clause+"hasAny("+columnName+", ?)", filter.GetHasAny())) + if len(filter.GetContainsAny()) > 0 { + mods = append(mods, qm.Where("hasAny("+columnName+", ?)", filter.GetContainsAny())) + } + if len(filter.GetContainsAll()) > 0 { + mods = append(mods, qm.Where("hasAll("+columnName+", ?)", filter.GetContainsAll())) } - if len(filter.GetHasAll()) > 0 { - mods = append(mods, qm.Where(clause+"hasAll("+columnName+", ?)", filter.GetHasAll())) + if len(filter.GetNotContainsAny()) > 0 { + mods = append(mods, qm.Where("NOT hasAny("+columnName+", ?)", filter.GetNotContainsAny())) + } + if len(filter.GetNotContainsAll()) > 0 { + mods = append(mods, qm.Where("NOT hasAll("+columnName+", ?)", filter.GetNotContainsAll())) } // Process OR condition recursively - if filter.GetOr() != nil { - orMods := appendArrayFilterMods(nil, filter.GetOr(), columnName) - if len(orMods) > 0 { - mods = append(mods, qm.Or2(qm.Expr(orMods...))) + for _, cond := range filter.GetOr() { + clauseMods := arrayFilterMods(cond, columnName) + if len(clauseMods) != 0 { + mods = append(mods, qm.Or2(qm.Expr(clauseMods...))) } } + if len(filter.GetOr()) != 0 { + mods = []qm.QueryMod{qm.Expr(mods...)} + } return mods } diff --git a/pkg/grpc/fetch-api.pb.go b/pkg/grpc/fetch-api.pb.go index 36d58aa..e4541ce 100644 --- a/pkg/grpc/fetch-api.pb.go +++ b/pkg/grpc/fetch-api.pb.go @@ -293,13 +293,15 @@ func (x *AdvancedSearchOptions) GetTags() *ArrayFilterOption { type ArrayFilterOption struct { state protoimpl.MessageState `protogen:"open.v1"` // Match if the field has any of these values. - HasAny []string `protobuf:"bytes,1,rep,name=has_any,json=hasAny,proto3" json:"has_any,omitempty"` + ContainsAny []string `protobuf:"bytes,1,rep,name=contains_any,json=containsAny,proto3" json:"contains_any,omitempty"` // Match if the field has all of these values. - HasAll []string `protobuf:"bytes,2,rep,name=has_all,json=hasAll,proto3" json:"has_all,omitempty"` - // Negate all matches in this filter. - Negate bool `protobuf:"varint,3,opt,name=negate,proto3" json:"negate,omitempty"` + ContainsAll []string `protobuf:"bytes,2,rep,name=contains_all,json=containsAll,proto3" json:"contains_all,omitempty"` + // Match if the field does not have any of these values. + NotContainsAny []string `protobuf:"bytes,3,rep,name=not_contains_any,json=notContainsAny,proto3" json:"not_contains_any,omitempty"` + // Match if the field does not have all of these values. + NotContainsAll []string `protobuf:"bytes,4,rep,name=not_contains_all,json=notContainsAll,proto3" json:"not_contains_all,omitempty"` // Additional filter condition to combine with this one using OR logic. - Or *ArrayFilterOption `protobuf:"bytes,4,opt,name=or,proto3" json:"or,omitempty"` + Or []*ArrayFilterOption `protobuf:"bytes,5,rep,name=or,proto3" json:"or,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -334,28 +336,35 @@ func (*ArrayFilterOption) Descriptor() ([]byte, []int) { return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{2} } -func (x *ArrayFilterOption) GetHasAny() []string { +func (x *ArrayFilterOption) GetContainsAny() []string { if x != nil { - return x.HasAny + return x.ContainsAny } return nil } -func (x *ArrayFilterOption) GetHasAll() []string { +func (x *ArrayFilterOption) GetContainsAll() []string { if x != nil { - return x.HasAll + return x.ContainsAll } return nil } -func (x *ArrayFilterOption) GetNegate() bool { +func (x *ArrayFilterOption) GetNotContainsAny() []string { if x != nil { - return x.Negate + return x.NotContainsAny } - return false + return nil } -func (x *ArrayFilterOption) GetOr() *ArrayFilterOption { +func (x *ArrayFilterOption) GetNotContainsAll() []string { + if x != nil { + return x.NotContainsAll + } + return nil +} + +func (x *ArrayFilterOption) GetOr() []*ArrayFilterOption { if x != nil { return x.Or } @@ -364,12 +373,12 @@ func (x *ArrayFilterOption) GetOr() *ArrayFilterOption { type StringFilterOption struct { state protoimpl.MessageState `protogen:"open.v1"` - // Match if the field has any of these values (OR logic) - HasAny []string `protobuf:"bytes,1,rep,name=has_any,json=hasAny,proto3" json:"has_any,omitempty"` - // Negate all matches in this filter. - Negate bool `protobuf:"varint,2,opt,name=negate,proto3" json:"negate,omitempty"` + // Match if the field is in the list of values. + In []string `protobuf:"bytes,1,rep,name=in,proto3" json:"in,omitempty"` + // Match if the field is not in the list of values. + NotIn []string `protobuf:"bytes,2,rep,name=not_in,json=notIn,proto3" json:"not_in,omitempty"` // Additional filter condition to combine with this one using OR logic. - Or *StringFilterOption `protobuf:"bytes,3,opt,name=or,proto3" json:"or,omitempty"` + Or []*StringFilterOption `protobuf:"bytes,3,rep,name=or,proto3" json:"or,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -404,21 +413,21 @@ func (*StringFilterOption) Descriptor() ([]byte, []int) { return file_pkg_grpc_fetch_api_proto_rawDescGZIP(), []int{3} } -func (x *StringFilterOption) GetHasAny() []string { +func (x *StringFilterOption) GetIn() []string { if x != nil { - return x.HasAny + return x.In } return nil } -func (x *StringFilterOption) GetNegate() bool { +func (x *StringFilterOption) GetNotIn() []string { if x != nil { - return x.Negate + return x.NotIn } - return false + return nil } -func (x *StringFilterOption) GetOr() *StringFilterOption { +func (x *StringFilterOption) GetOr() []*StringFilterOption { if x != nil { return x.Or } @@ -1065,16 +1074,17 @@ const file_pkg_grpc_fetch_api_proto_rawDesc = "" + "\x06extras\x18\n" + " \x01(\v2\x18.grpc.StringFilterOptionR\x06extras\x12(\n" + "\x02id\x18\v \x01(\v2\x18.grpc.StringFilterOptionR\x02id\x12+\n" + - "\x04tags\x18\f \x01(\v2\x17.grpc.ArrayFilterOptionR\x04tags\"\x86\x01\n" + - "\x11ArrayFilterOption\x12\x17\n" + - "\ahas_any\x18\x01 \x03(\tR\x06hasAny\x12\x17\n" + - "\ahas_all\x18\x02 \x03(\tR\x06hasAll\x12\x16\n" + - "\x06negate\x18\x03 \x01(\bR\x06negate\x12'\n" + - "\x02or\x18\x04 \x01(\v2\x17.grpc.ArrayFilterOptionR\x02or\"o\n" + - "\x12StringFilterOption\x12\x17\n" + - "\ahas_any\x18\x01 \x03(\tR\x06hasAny\x12\x16\n" + - "\x06negate\x18\x02 \x01(\bR\x06negate\x12(\n" + - "\x02or\x18\x03 \x01(\v2\x18.grpc.StringFilterOptionR\x02or\"m\n" + + "\x04tags\x18\f \x01(\v2\x17.grpc.ArrayFilterOptionR\x04tags\"\xd6\x01\n" + + "\x11ArrayFilterOption\x12!\n" + + "\fcontains_any\x18\x01 \x03(\tR\vcontainsAny\x12!\n" + + "\fcontains_all\x18\x02 \x03(\tR\vcontainsAll\x12(\n" + + "\x10not_contains_any\x18\x03 \x03(\tR\x0enotContainsAny\x12(\n" + + "\x10not_contains_all\x18\x04 \x03(\tR\x0enotContainsAll\x12'\n" + + "\x02or\x18\x05 \x03(\v2\x17.grpc.ArrayFilterOptionR\x02or\"e\n" + + "\x12StringFilterOption\x12\x0e\n" + + "\x02in\x18\x01 \x03(\tR\x02in\x12\x15\n" + + "\x06not_in\x18\x02 \x03(\tR\x05notIn\x12(\n" + + "\x02or\x18\x03 \x03(\v2\x18.grpc.StringFilterOptionR\x02or\"m\n" + "\x0fCloudEventIndex\x124\n" + "\x06header\x18\x01 \x01(\v2\x1c.cloudevent.CloudEventHeaderR\x06header\x12$\n" + "\x04data\x18\x02 \x01(\v2\x10.grpc.ObjectInfoR\x04data\"\x1e\n" + diff --git a/pkg/grpc/fetch-api.proto b/pkg/grpc/fetch-api.proto index b127bad..72842c7 100644 --- a/pkg/grpc/fetch-api.proto +++ b/pkg/grpc/fetch-api.proto @@ -101,25 +101,31 @@ message AdvancedSearchOptions { // every thing is implicitly ANDed together. message ArrayFilterOption { // Match if the field has any of these values. - repeated string has_any = 1; - + repeated string contains_any = 1; + // Match if the field has all of these values. - repeated string has_all = 2; - - // Negate all matches in this filter. - bool negate = 3; - + repeated string contains_all = 2; + + // Match if the field does not have any of these values. + repeated string not_contains_any = 3; + + // Match if the field does not have all of these values. + repeated string not_contains_all = 4; + // Additional filter condition to combine with this one using OR logic. - ArrayFilterOption or = 4; + repeated ArrayFilterOption or = 5; + } message StringFilterOption { - // Match if the field has any of these values (OR logic) - repeated string has_any = 1; - // Negate all matches in this filter. - bool negate = 2; + // Match if the field is in the list of values. + repeated string in = 1; + + // Match if the field is not in the list of values. + repeated string not_in = 2; + // Additional filter condition to combine with this one using OR logic. - StringFilterOption or = 3; + repeated StringFilterOption or = 3; } message CloudEventIndex {