statedb: handle empty index name in queries - #169
Conversation
|
|
|
||
| func (t *genTable[Obj]) indexPos(name string) int { | ||
| // An empty index name refers to the primary index, matching getIndexer. | ||
| if name == "" { |
There was a problem hiding this comment.
indexPos is executed for secondary indexes too. In case of a secondary index with an empty name we'll enter this if returning the primary index.
I think we should explicitly reject secondary indexes with empty names in NewTableAny.
There was a problem hiding this comment.
I think we should explicitly reject secondary indexes with empty names in
NewTableAny.
I think rejecting secondary indexes with empty names is a worthwhile change. Fixed as suggested.
But AFAICS it still won't fix the issue here in all cases because the name passed to indexPos by callers may be the one extracted from Query.index or QueryRequest.Index and that could still be empty in some cases, depending on how the query was constructed.
There was a problem hiding this comment.
I see.
Besides, looking again at the code I see that stateDB explicitly supports objects with an empty primary key (see TestDB_EmptyKeys) and with this change it is possible to have something like this:
emptyPrefixIndex := NetIPPrefixIndex[*testObject]{
Name: "empty-prefix",
Unique: true,
FromObject: func(*testObject) iter.Seq[netip.Prefix] {
return func(func(netip.Prefix) bool) {}
},
}
table, err := NewTable(
db,
"test",
keyIndex, // Primary index
emptyPrefixIndex, // Secondary index
)Now suppose that this object with an empty primary key is in the table:
testObject{
Key: "",
Tags: part.NewSet("test-object"),
}Then this query:
queryObject := &testObject{Key: "unrelated"}
q := emptyPrefixIndex.QueryFromObject(queryObject)
table.Get(rtxn, q)returns the stored testObject, despite the object we used to generate the query had a Key value equal to "unrelated" (IOW: from a primary key POV it should not match).
Even if I don't think it is dangerous, it feels inconsistent. Getting this right seems hard, that's why I wonder if we should instead return a "no-match" representation instead of steering an empty index name toward the primary index.
There was a problem hiding this comment.
Thanks, that's a good point. I didn't consider this in my original change. I agree that getting this right is rather tricky, so I'll move the PR to draft for now and will discuss it with @joamaki after he's back.
Validate that the primary index is unique before starting to construct any objects. Nothing in the check depends on these being present. Signed-off-by: Tobias Klauser <tobias@cilium.io>
Suggested-by: Fabio Falzoi <fabio.falzoi@isovalent.com> Signed-off-by: Tobias Klauser <tobias@cilium.io>
Querying a table with an empty index name currently panics with an "index out of range" runtime error. This is because (*genTable[Obj]).indexPos reads name[0] without a length check. This is reachable from normal API usage: NetIPPrefixIndex.QueryFromObject and LPMIndex.QueryFromObject return a zero-value Query (with an empty index name) when the object yields no prefixes or keys, and passing such a query to Get, List, Prefix or LowerBound panics. Fix this by treating an empty index name as a reference to the primary index in indexPos, following the existing behavior in getIndexer. Signed-off-by: Tobias Klauser <tobias@cilium.io>
fe1d212 to
7ded532
Compare
Querying a table with an empty index name currently panics with an "index out of range" runtime error. This is because
(*genTable[Obj]).indexPosreadsname[0]without a length check.This is reachable from normal API usage:
NetIPPrefixIndex.QueryFromObjectandLPMIndex.QueryFromObjectreturn a zero-value Query (with an empty index name) when the object yields no prefixes or keys, and passing such a query toGet,List,PrefixorLowerBoundpanics.Fix this by treating an empty index name as a reference to the primary index in
indexPos, following the existing behavior ingetIndexer.