1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-08 18:59:58 +00:00

Add support for OR filters

Currently, multiple filters can be appended on the query string, and
each subsequent filter is ANDed with the set. Items that pass through
the filter set must match every filter in the set.

This change adds support for OR filters. A single filter key can specify
multiple filters, separated by ','. An item that passes this filter can
match any filter in the set.

For example, this filter matches items that have either "name" or
"namespace" that match "example":

?filter=metadata.name=example,metadata.namespace=example

This filter matches items that have "name" that matches either "foo" or
"bar":

?filter=metadata.name=foo,metadata.name=bar

Specifying more than one filter key in the query still ANDs each inner
filter set together. This set of filters can match either a name of
"foo" or "bar", but must in all cases match namespace "abc":

?filter=metadata.name=foo,metadata.name=bar&filter=metadata.namespace=abc
This commit is contained in:
Colleen Murphy
2023-02-16 15:32:29 -08:00
parent 53fbb87f59
commit 2e4ee872d9
4 changed files with 547 additions and 50 deletions

View File

@@ -278,6 +278,9 @@ func TestList(t *testing.T) {
apiOps: []*types.APIRequest{
newRequest("filter=data.color=green", "user1"),
newRequest("filter=data.color=green&filter=metadata.name=bramley", "user1"),
newRequest("filter=data.color=green,data.color=pink", "user1"),
newRequest("filter=data.color=green,data.color=pink&filter=metadata.name=fuji", "user1"),
newRequest("filter=data.color=green,data.color=pink&filter=metadata.name=crispin", "user1"),
},
access: []map[string]string{
{
@@ -286,6 +289,15 @@ func TestList(t *testing.T) {
{
"user1": "roleA",
},
{
"user1": "roleA",
},
{
"user1": "roleA",
},
{
"user1": "roleA",
},
},
partitions: map[string][]Partition{
"user1": {
@@ -318,6 +330,23 @@ func TestList(t *testing.T) {
newApple("bramley").toObj(),
},
},
{
Count: 3,
Objects: []types.APIObject{
newApple("fuji").toObj(),
newApple("granny-smith").toObj(),
newApple("bramley").toObj(),
},
},
{
Count: 1,
Objects: []types.APIObject{
newApple("fuji").toObj(),
},
},
{
Count: 0,
},
},
},
{
@@ -1739,6 +1768,133 @@ func TestList(t *testing.T) {
{"green": 2},
},
},
{
name: "pagination with or filters",
apiOps: []*types.APIRequest{
newRequest("filter=metadata.name=el,data.color=el&pagesize=2", "user1"),
newRequest("filter=metadata.name=el,data.color=el&pagesize=2&page=2&revision=42", "user1"),
newRequest("filter=metadata.name=el,data.color=el&pagesize=2&page=3&revision=42", "user1"),
},
access: []map[string]string{
{
"user1": "roleA",
},
{
"user1": "roleA",
},
{
"user1": "roleA",
},
},
partitions: map[string][]Partition{
"user1": {
mockPartition{
name: "all",
},
},
},
objects: map[string]*unstructured.UnstructuredList{
"all": {
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"resourceVersion": "42",
},
},
Items: []unstructured.Unstructured{
newApple("fuji").Unstructured,
newApple("granny-smith").Unstructured,
newApple("red-delicious").Unstructured,
newApple("golden-delicious").Unstructured,
newApple("crispin").Unstructured,
},
},
},
want: []types.APIObjectList{
{
Count: 3,
Pages: 2,
Revision: "42",
Objects: []types.APIObject{
newApple("red-delicious").toObj(),
newApple("golden-delicious").toObj(),
},
},
{
Count: 3,
Pages: 2,
Revision: "42",
Objects: []types.APIObject{
newApple("crispin").toObj(),
},
},
{
Count: 3,
Pages: 2,
Revision: "42",
},
},
wantCache: []mockCache{
{
contents: map[cacheKey]*unstructured.UnstructuredList{
{
chunkSize: 100000,
filters: "data.color=el,metadata.name=el",
pageSize: 2,
accessID: getAccessID("user1", "roleA"),
resourcePath: "/apples",
revision: "42",
}: {
Items: []unstructured.Unstructured{
newApple("red-delicious").Unstructured,
newApple("golden-delicious").Unstructured,
newApple("crispin").Unstructured,
},
},
},
},
{
contents: map[cacheKey]*unstructured.UnstructuredList{
{
chunkSize: 100000,
filters: "data.color=el,metadata.name=el",
pageSize: 2,
accessID: getAccessID("user1", "roleA"),
resourcePath: "/apples",
revision: "42",
}: {
Items: []unstructured.Unstructured{
newApple("red-delicious").Unstructured,
newApple("golden-delicious").Unstructured,
newApple("crispin").Unstructured,
},
},
},
},
{
contents: map[cacheKey]*unstructured.UnstructuredList{
{
chunkSize: 100000,
filters: "data.color=el,metadata.name=el",
pageSize: 2,
accessID: getAccessID("user1", "roleA"),
resourcePath: "/apples",
revision: "42",
}: {
Items: []unstructured.Unstructured{
newApple("red-delicious").Unstructured,
newApple("golden-delicious").Unstructured,
newApple("crispin").Unstructured,
},
},
},
},
},
wantListCalls: []map[string]int{
{"all": 1},
{"all": 1},
{"all": 1},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {