1
0
mirror of https://github.com/rancher/steve.git synced 2025-08-31 06:46:25 +00:00

Add more doc on the indirect operator to the sql README.

Move types related to list options and sql queries into their own package.

The problem having these in the informer package is that eventually code
in other packages will need to import `informer` only for constants or types,
but some members of the informer package may already depend on those. Best to
move type definitions into their own simpler package.

Fix the ListOptions sort field.

Instead of making it a single array-ish field, convert it into a
true array of Sort Directives.  Easier to read, less bending backwards.

Pass the listOptions struct by reference to avoid copying.

We never update the ListOptions struct once it's created so there's
no need to pass it by value.

This might be a near-useless optimization...

Add parsing of the indirect access operator ('=>').

Remove a debug stmt.
This commit is contained in:
Eric Promislow
2025-04-25 14:22:48 -07:00
parent 28ab9021ff
commit 78b2dc9401
3 changed files with 36 additions and 34 deletions

View File

@@ -156,3 +156,35 @@ of a query that may be user supplied, such as columns, should be carefully valid
### Troubleshooting SQLite
A useful tool for troubleshooting the database files is the sqlite command line tool. Another useful tool is the goland
sqlite plugin. Both of these tools can be used with the database files.
### Indirect Querying
You can do filtering and sorting on the current table (which backs the resource name after `/v1/` in the URL)
based on related values in another table using indirect indexing. This works for both sorting and filtering.
This assumes that the main table has a field, call it field F1, with a 1:1 relation with a field F2 on some other table T2.
We then can access some other field F3 on the selected row of T2 based on F1, and then operate on that value F3.
Sorting will sort on that value (either ascending or descending), and operators will do boolean operations on that value
and compare the result against field F1.
Let's look at a specific example: sort namespaces based on the human-friendly name of the associated project's cluster:
`sort=metadata.labels[field.cattle.io/projectId] => [management.cattle.io/v3][Project][metadata.name][spec.clusterName],metadata.name`
Normally the namespaces are displayed in order of their internal name (`metadata.name`), but the above command groups them each according to the name of their enclosing cluster, using the friendly name of the cluster (`local` for the local cluster, and whatever name the user gave when creating downstream clusters).
Staying on this particular query, you can show only namespaces in the local cluster with this query:
`filter=metadata.labels[field.cattle.io/projectId] => [management.cattle.io/v3][Project][metadata.name][spec.clusterName] = local`
or show all the other namespaces:
`filter=metadata.labels[field.cattle.io/projectId] => [management.cattle.io/v3][Project][metadata.name][spec.clusterName] != local`
An implementation note: some namespaces might not have a label `field.cattle.io/projectId`. With only the sort command, these namespaces show up in the null-clusterName group after all the other groups. If you don't want to show these namespaces, you can combine a filter and a sort:
`filter=metadata.labels[field.cattle.io/projectId]&sort=metadata.labels[field.cattle.io/projectId] => [management.cattle.io/v3][Project][metadata.name][spec.clusterName],metadata.name`
The filter command uses the implicit `EXISTS` operator, available only for labels. This query selects only those namespaces that have the specied label, and the sort operates on those selected fields.
#### General Syntax
The syntax for the part to the left of the `=>` operator is the same as for other operators. The external reference, to the right of that operator, has exactly four bracketed parts: `[GROUP/API][KIND][FOREIGN-KEY-FIELD][TARGET-FIELD]`. For core Kubernetes types, leave `GROUP` empty. `KIND` is usually a capitalized singular noun. The two field names can use both dotted-accessor and square-bracket notation.

View File

@@ -73,14 +73,11 @@ func k8sRequirementToOrFilter(requirement queryparser.Requirement) (sqltypes.Fil
values := requirement.Values()
queryFields := splitQuery(requirement.Key())
op, usePartialMatch, err := k8sOpToRancherOp(requirement.Operator())
isIndirect, indirectFields := requirement.IndirectInfo()
return sqltypes.Filter{
Field: queryFields,
Matches: values,
Op: op,
Partial: usePartialMatch,
IsIndirect: isIndirect,
IndirectFields: indirectFields,
Field: queryFields,
Matches: values,
Op: op,
Partial: usePartialMatch,
}, err
}

View File

@@ -370,33 +370,6 @@ func TestParseQuery(t *testing.T) {
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with an indirect labels filter param should create an indirect labels-specific filter.",
req: &types.APIRequest{
Request: &http.Request{
URL: &url.URL{RawQuery: "filter=metadata.labels[grover.example.com/fish]=>[_v1][Foods][foodCode][country]=japan"},
},
},
expectedLO: sqltypes.ListOptions{
ChunkSize: defaultLimit,
Filters: []sqltypes.OrFilter{
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "labels", "grover.example.com/fish"},
Matches: []string{"japan"},
Op: sqltypes.Eq,
IsIndirect: true,
IndirectFields: []string{"_v1", "Foods", "foodCode", "country"},
},
},
},
},
Pagination: sqltypes.Pagination{
Page: 1,
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with multiple filter params, should include multiple or filters.",
req: &types.APIRequest{