diff --git a/pkg/policy/grammar.ebnf b/pkg/policy/grammar.ebnf index 1b277473..a27c9cf6 100644 --- a/pkg/policy/grammar.ebnf +++ b/pkg/policy/grammar.ebnf @@ -15,7 +15,7 @@ CbtStmt ::= 'CBF', Number1 (* container backup factor *) SelectStmt ::= 'SELECT', Number1, (* number of nodes to select without container backup factor *) - 'IN', Clause?, Ident, (* bucket name *) + ('IN', Clause?, Ident)?, (* bucket name *) FROM, (Ident | '*'), (* filter reference or whole netmap *) ('AS', Ident)? (* optional selector name *) ; diff --git a/pkg/policy/grammar.go b/pkg/policy/grammar.go index 1bbbd61a..0a7ce7f9 100644 --- a/pkg/policy/grammar.go +++ b/pkg/policy/grammar.go @@ -27,11 +27,10 @@ type replicaStmt struct { } type selectorStmt struct { - Count uint32 `"SELECT" @Int` - Clause string `"IN" @("SAME" | "DISTINCT")?` - Bucket string `@Ident` - Filter string `"FROM" @(Ident | "*")` - Name string `("AS" @Ident)?` + Count uint32 `"SELECT" @Int` + Bucket []string `("IN" @(("SAME" | "DISTINCT")? Ident))?` + Filter string `"FROM" @(Ident | "*")` + Name string `("AS" @Ident)?` } type filterStmt struct { diff --git a/pkg/policy/query.go b/pkg/policy/query.go index 8d5c2dce..d4412826 100644 --- a/pkg/policy/query.go +++ b/pkg/policy/query.go @@ -52,18 +52,16 @@ func Parse(s string) (*netmap.PlacementPolicy, error) { return nil, fmt.Errorf("%w: '%s'", ErrUnknownFilter, qs.Filter) } s := new(netmap.Selector) - switch qs.Clause { - case "SAME": - s.SetClause(netmap.Same) - case "DISTINCT": - s.SetClause(netmap.Distinct) - default: - s.SetClause(netmap.UnspecifiedClause) + switch len(qs.Bucket) { + case 1: // only bucket + s.SetAttribute(qs.Bucket[0]) + case 2: // clause + bucket + s.SetClause(clauseFromString(qs.Bucket[0])) + s.SetAttribute(qs.Bucket[1]) } s.SetName(qs.Name) seenSelectors[qs.Name] = true s.SetFilter(qs.Filter) - s.SetAttribute(qs.Bucket) if qs.Count == 0 { return nil, fmt.Errorf("%w: SELECT", ErrInvalidNumber) } @@ -95,6 +93,17 @@ func Parse(s string) (*netmap.PlacementPolicy, error) { return p, nil } +func clauseFromString(s string) netmap.Clause { + switch strings.ToUpper(s) { + case "SAME": + return netmap.Same + case "DISTINCT": + return netmap.Distinct + default: + return netmap.UnspecifiedClause + } +} + func filterFromOrChain(expr *orChain, seen map[string]bool) (*netmap.Filter, error) { var fs []*netmap.Filter for _, ac := range expr.Clauses { diff --git a/pkg/policy/query_test.go b/pkg/policy/query_test.go index b15c33f3..54750067 100644 --- a/pkg/policy/query_test.go +++ b/pkg/policy/query_test.go @@ -48,6 +48,39 @@ SELECT 1 IN City FROM * AS SPB` require.Equal(t, expected, r) } +// https://github.com/nspcc-dev/neofs-node/issues/46 +func TestFromSelectNoAttribute(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + q := `REP 2 + SELECT 6 FROM *` + + expected := new(netmap.PlacementPolicy) + expected.SetFilters([]*netmap.Filter{}) + expected.SetSelectors([]*netmap.Selector{newSelector(6, netmap.UnspecifiedClause, "", "*", "")}) + expected.SetReplicas([]*netmap.Replica{newReplica("", 2)}) + + r, err := Parse(q) + require.NoError(t, err) + require.Equal(t, expected, r) + + }) + + t.Run("with filter", func(t *testing.T) { + q := `REP 2 + SELECT 6 FROM F + FILTER StorageType EQ SSD AS F` + + expected := new(netmap.PlacementPolicy) + expected.SetFilters([]*netmap.Filter{newFilter("F", "StorageType", "SSD", netmap.EQ)}) + expected.SetSelectors([]*netmap.Selector{newSelector(6, netmap.UnspecifiedClause, "", "F", "")}) + expected.SetReplicas([]*netmap.Replica{newReplica("", 2)}) + + r, err := Parse(q) + require.NoError(t, err) + require.Equal(t, expected, r) + }) +} + func TestFromSelectClause(t *testing.T) { q := `REP 4 SELECT 3 IN Country FROM *