frostfs-sdk-go/netmap/filter.go
Pavel Pogodaev 6ce73790ea [#276] Merge repo with frostfs-api-go
Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
2024-10-22 14:05:12 +00:00

165 lines
3.9 KiB
Go

package netmap
import (
"fmt"
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
)
// mainFilterName is a name of the filter
// which points to the whole netmap.
const mainFilterName = "*"
const likeWildcard = "*"
// processFilters processes filters and returns error is any of them is invalid.
func (c *context) processFilters(p PlacementPolicy) error {
for i := range p.filters {
if err := c.processFilter(p.filters[i], true); err != nil {
return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err)
}
}
return nil
}
func (c *context) processFilter(f netmap.Filter, top bool) error {
fName := f.GetName()
if fName == mainFilterName {
return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName)
}
if top && fName == "" {
return errUnnamedTopFilter
}
if !top && fName != "" && c.processedFilters[fName] == nil {
return errFilterNotFound
}
inner := f.GetFilters()
switch op := f.GetOp(); op {
case netmap.AND, netmap.OR, netmap.NOT:
for i := range inner {
if err := c.processFilter(inner[i], false); err != nil {
return fmt.Errorf("process inner filter #%d: %w", i, err)
}
}
default:
if len(inner) != 0 {
return errNonEmptyFilters
} else if !top && fName != "" { // named reference
return nil
}
switch op {
case netmap.EQ, netmap.NE, netmap.LIKE:
case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
val := f.GetValue()
n, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue())
}
c.numCache[val] = n
default:
return fmt.Errorf("%w: %s", errInvalidFilterOp, op)
}
}
if top {
c.processedFilters[fName] = &f
}
return nil
}
// match matches f against b. It returns no errors because
// filter should have been parsed during context creation
// and missing node properties are considered as a regular fail.
func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
switch f.GetOp() {
case netmap.NOT:
inner := f.GetFilters()
fSub := &inner[0]
if name := inner[0].GetName(); name != "" {
fSub = c.processedFilters[name]
}
return !c.match(fSub, b)
case netmap.AND, netmap.OR:
inner := f.GetFilters()
for i := range inner {
fSub := &inner[i]
if name := inner[i].GetName(); name != "" {
fSub = c.processedFilters[name]
}
ok := c.match(fSub, b)
if ok == (f.GetOp() == netmap.OR) {
return ok
}
}
return f.GetOp() == netmap.AND
default:
return c.matchKeyValue(f, b)
}
}
func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool {
switch op := f.GetOp(); op {
case netmap.EQ:
return b.Attribute(f.GetKey()) == f.GetValue()
case netmap.LIKE:
str, prefix := strings.CutPrefix(f.GetValue(), likeWildcard)
str, suffix := strings.CutSuffix(str, likeWildcard)
if prefix && suffix {
return strings.Contains(b.Attribute(f.GetKey()), str)
}
if prefix && !suffix {
return strings.HasSuffix(b.Attribute(f.GetKey()), str)
}
if !prefix && suffix {
return strings.HasPrefix(b.Attribute(f.GetKey()), str)
}
return b.Attribute(f.GetKey()) == f.GetValue()
case netmap.NE:
return b.Attribute(f.GetKey()) != f.GetValue()
default:
var attr uint64
switch f.GetKey() {
case attrPrice:
attr = b.Price()
case attrCapacity:
attr = b.capacity()
default:
var err error
attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
if err != nil {
// Note: because filters are somewhat independent from nodes attributes,
// We don't report an error here, and fail filter instead.
return false
}
}
switch op {
case netmap.GT:
return attr > c.numCache[f.GetValue()]
case netmap.GE:
return attr >= c.numCache[f.GetValue()]
case netmap.LT:
return attr < c.numCache[f.GetValue()]
case netmap.LE:
return attr <= c.numCache[f.GetValue()]
default:
// do nothing and return false
}
}
// will not happen if context was created from f (maybe panic?)
return false
}