package netmap import ( "errors" "fmt" "io" "strconv" "strings" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/parser" "github.com/antlr4-go/antlr/v4" ) // PlacementPolicy declares policy to store objects in the FrostFS container. // Within itself, PlacementPolicy represents a set of rules to select a subset // of nodes from FrostFS network map - node-candidates for object storage. // // PlacementPolicy is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap.PlacementPolicy // message. See ReadFromV2 / WriteToV2 methods. // // Instances can be created using built-in var declaration. type PlacementPolicy struct { backupFactor uint32 filters []netmap.Filter selectors []netmap.Selector replicas []netmap.Replica unique bool } func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresence bool) error { p.replicas = m.GetReplicas() if checkFieldPresence { if len(p.replicas) == 0 { return errors.New("missing replicas") } if len(p.replicas) != 1 { for i := range p.replicas { if p.replicas[i].GetECDataCount() != 0 || p.replicas[i].GetECParityCount() != 0 { return errors.New("erasure code group must be used exclusively") } } } } p.backupFactor = m.GetContainerBackupFactor() p.selectors = m.GetSelectors() p.filters = m.GetFilters() p.unique = m.GetUnique() return nil } // Marshal encodes PlacementPolicy into a binary format of the FrostFS API // protocol (Protocol Buffers with direct field order). // // See also Unmarshal. func (p PlacementPolicy) Marshal() []byte { var m netmap.PlacementPolicy p.WriteToV2(&m) return m.StableMarshal(nil) } // Unmarshal decodes FrostFS API protocol binary format into the PlacementPolicy // (Protocol Buffers with direct field order). Returns an error describing // a format violation. // // See also Marshal. func (p *PlacementPolicy) Unmarshal(data []byte) error { var m netmap.PlacementPolicy err := m.Unmarshal(data) if err != nil { return err } return p.readFromV2(m, false) } // MarshalJSON encodes PlacementPolicy into a JSON format of the FrostFS API // protocol (Protocol Buffers JSON). // // See also UnmarshalJSON. func (p PlacementPolicy) MarshalJSON() ([]byte, error) { var m netmap.PlacementPolicy p.WriteToV2(&m) return m.MarshalJSON() } // UnmarshalJSON decodes FrostFS API protocol JSON format into the PlacementPolicy // (Protocol Buffers JSON). Returns an error describing a format violation. // // See also MarshalJSON. func (p *PlacementPolicy) UnmarshalJSON(data []byte) error { var m netmap.PlacementPolicy err := m.UnmarshalJSON(data) if err != nil { return err } return p.readFromV2(m, false) } // ReadFromV2 reads PlacementPolicy from the netmap.PlacementPolicy message. // Checks if the message conforms to FrostFS API V2 protocol. // // See also WriteToV2. func (p *PlacementPolicy) ReadFromV2(m netmap.PlacementPolicy) error { return p.readFromV2(m, true) } // WriteToV2 writes PlacementPolicy to the session.Token message. // The message must not be nil. // // See also ReadFromV2. func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) { m.SetContainerBackupFactor(p.backupFactor) m.SetFilters(p.filters) m.SetSelectors(p.selectors) m.SetReplicas(p.replicas) m.SetUnique(p.unique) } // ReplicaDescriptor replica descriptor characterizes replicas of objects from // the subset selected by a particular Selector. type ReplicaDescriptor struct { m netmap.Replica } // SetNumberOfObjects sets number of object replicas. func (r *ReplicaDescriptor) SetNumberOfObjects(c uint32) { r.m.SetCount(c) } func (r *ReplicaDescriptor) SetECDataCount(v uint32) { r.m.SetECDataCount(v) } func (r *ReplicaDescriptor) SetECParityCount(v uint32) { r.m.SetECParityCount(v) } // NumberOfObjects returns number set using SetNumberOfObjects. // // Zero ReplicaDescriptor has zero number of objects. func (r ReplicaDescriptor) NumberOfObjects() uint32 { return r.m.GetCount() } func (r ReplicaDescriptor) GetECDataCount() uint32 { return r.m.GetECDataCount() } func (r ReplicaDescriptor) GetECParityCount() uint32 { return r.m.GetECParityCount() } // TotalECPartCount returns total sum of ECDataCount and ECParityCount. func (r ReplicaDescriptor) TotalECPartCount() uint32 { return r.m.GetECDataCount() + r.m.GetECParityCount() } // SetSelectorName sets name of the related Selector. // // Zero ReplicaDescriptor references to the root bucket's selector: it contains // all possible nodes to store the object. func (r *ReplicaDescriptor) SetSelectorName(s string) { r.m.SetSelector(s) } // AddReplicas adds a bunch object replica's characteristics. // // See also IterateReplicas. func (p *PlacementPolicy) AddReplicas(rs ...ReplicaDescriptor) { off := len(p.replicas) p.replicas = append(p.replicas, make([]netmap.Replica, len(rs))...) for i := range rs { p.replicas[off+i] = rs[i].m } } // NumberOfReplicas returns number of replica descriptors set using AddReplicas. // // Zero PlacementPolicy has no replicas which is incorrect according to the // FrostFS API protocol. func (p PlacementPolicy) NumberOfReplicas() int { return len(p.replicas) } // ReplicaNumberByIndex returns number of object replicas from the i-th replica // descriptor. Index MUST be in range [0; NumberOfReplicas()). // // Zero PlacementPolicy has no replicas. // // Deprecated: Use PlacementPolicy.ReplicaDescriptor(int).NumberOfObjects() instead. func (p PlacementPolicy) ReplicaNumberByIndex(i int) uint32 { return p.replicas[i].GetCount() } // ReplicaDescriptor returns i-th replica descriptor. Index MUST be in range [0; NumberOfReplicas()). func (p PlacementPolicy) ReplicaDescriptor(i int) ReplicaDescriptor { return ReplicaDescriptor{ m: p.replicas[i], } } // SetContainerBackupFactor sets container backup factor: it controls how deep // FrostFS will search for nodes alternatives to include into container's nodes subset. // // Zero PlacementPolicy has zero container backup factor. func (p *PlacementPolicy) SetContainerBackupFactor(f uint32) { p.backupFactor = f } // SetUnique sets the unique flag: it controls whether the selected replica buckets // are disjoint or not. // // Zero PlacementPolicy has false unique flag. func (p *PlacementPolicy) SetUnique(b bool) { p.unique = b } // Selector describes the bucket selection operator: choose a number of nodes // from the bucket taking the nearest nodes to the related container by hash distance. type Selector struct { m netmap.Selector } // SetName sets name with which the Selector can be referenced. // // Zero Selector is unnamed. func (s *Selector) SetName(name string) { s.m.SetName(name) } // SetNumberOfNodes sets number of nodes to select from the bucket. // // Zero Selector selects nothing. func (s *Selector) SetNumberOfNodes(num uint32) { s.m.SetCount(num) } // SelectByBucketAttribute sets attribute of the bucket to select nodes from. // // Zero Selector has empty attribute. func (s *Selector) SelectByBucketAttribute(bucket string) { s.m.SetAttribute(bucket) } // SetClause sets the clause for the Selector. func (s *Selector) SetClause(clause netmap.Clause) { s.m.SetClause(clause) } // SelectSame makes selection algorithm to select only nodes having the same values // of the bucket attribute. // // Zero Selector doesn't specify selection modifier so nodes are selected randomly. // // See also SelectByBucketAttribute. func (s *Selector) SelectSame() { s.m.SetClause(netmap.Same) } // SelectDistinct makes selection algorithm to select only nodes having the different values // of the bucket attribute. // // Zero Selector doesn't specify selection modifier so nodes are selected randomly. // // See also SelectByBucketAttribute. func (s *Selector) SelectDistinct() { s.m.SetClause(netmap.Distinct) } // SetFilterName sets reference to pre-filtering nodes for selection. // // Zero Selector has no filtering reference. // // See also Filter.SetName. func (s *Selector) SetFilterName(f string) { s.m.SetFilter(f) } // AddSelectors adds a Selector bunch to form the subset of the nodes // to store container objects. // // Zero PlacementPolicy does not declare selectors. func (p *PlacementPolicy) AddSelectors(ss ...Selector) { off := len(p.selectors) p.selectors = append(p.selectors, make([]netmap.Selector, len(ss))...) for i := range ss { p.selectors[off+i] = ss[i].m } } // Filter contains rules for filtering the node sets. type Filter struct { m netmap.Filter } // SetName sets name with which the Filter can be referenced or, for inner filters, // to which the Filter references. Top-level filters MUST be named. The name // MUST NOT be '*'. // // Zero Filter is unnamed. func (x *Filter) SetName(name string) { x.m.SetName(name) } func (x *Filter) setAttribute(key string, op netmap.Operation, val string) { x.m.SetKey(key) x.m.SetOp(op) x.m.SetValue(val) } // Like applies the rule to accept only nodes with attribute like value. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) Like(key, value string) { x.setAttribute(key, netmap.LIKE, value) } // Equal applies the rule to accept only nodes with the same attribute value. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) Equal(key, value string) { x.setAttribute(key, netmap.EQ, value) } // NotEqual applies the rule to accept only nodes with the distinct attribute value. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NotEqual(key, value string) { x.setAttribute(key, netmap.NE, value) } // NumericGT applies the rule to accept only nodes with the numeric attribute // greater than given number. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericGT(key string, num int64) { x.setAttribute(key, netmap.GT, strconv.FormatInt(num, 10)) } // NumericGE applies the rule to accept only nodes with the numeric attribute // greater than or equal to given number. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericGE(key string, num int64) { x.setAttribute(key, netmap.GE, strconv.FormatInt(num, 10)) } // NumericLT applies the rule to accept only nodes with the numeric attribute // less than given number. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericLT(key string, num int64) { x.setAttribute(key, netmap.LT, strconv.FormatInt(num, 10)) } // NumericLE applies the rule to accept only nodes with the numeric attribute // less than or equal to given number. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericLE(key string, num int64) { x.setAttribute(key, netmap.LE, strconv.FormatInt(num, 10)) } func (x *Filter) setInnerFilters(op netmap.Operation, filters []Filter) { x.setAttribute("", op, "") inner := x.m.GetFilters() if rem := len(filters) - len(inner); rem > 0 { inner = append(inner, make([]netmap.Filter, rem)...) } for i := range filters { inner[i] = filters[i].m } x.m.SetFilters(inner) } // LogicalOR applies the rule to accept only nodes which satisfy at least one // of the given filters. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) LogicalOR(filters ...Filter) { x.setInnerFilters(netmap.OR, filters) } // LogicalAND applies the rule to accept only nodes which satisfy all the given // filters. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) LogicalAND(filters ...Filter) { x.setInnerFilters(netmap.AND, filters) } // AddFilters adds a Filter bunch that will be applied when selecting nodes. // // Zero PlacementPolicy has no filters. func (p *PlacementPolicy) AddFilters(fs ...Filter) { off := len(p.filters) p.filters = append(p.filters, make([]netmap.Filter, len(fs))...) for i := range fs { p.filters[off+i] = fs[i].m } } // WriteStringTo encodes PlacementPolicy into human-readably query and writes // the result into w. Returns w's errors directly. // // See also DecodeString. // nolint: funlen func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { if p.unique { if _, err := w.WriteString("UNIQUE\n"); err != nil { return err } } delim := "" for i := range p.replicas { c := p.replicas[i].GetCount() s := p.replicas[i].GetSelector() if c != 0 { _, err = w.WriteString(fmt.Sprintf("%sREP %d", delim, c)) } else { ecx, ecy := p.replicas[i].GetECDataCount(), p.replicas[i].GetECParityCount() _, err = w.WriteString(fmt.Sprintf("%sEC %d.%d", delim, ecx, ecy)) } if s != "" { _, err = w.WriteString(fmt.Sprintf(" IN %s", s)) } if err != nil { return err } delim = "\n" } if p.backupFactor > 0 { _, err = w.WriteString(fmt.Sprintf("\nCBF %d", p.backupFactor)) if err != nil { return err } } var s string for i := range p.selectors { _, err = w.WriteString(fmt.Sprintf("\nSELECT %d", p.selectors[i].GetCount())) if err != nil { return err } if s = p.selectors[i].GetAttribute(); s != "" { var clause string switch p.selectors[i].GetClause() { case netmap.Same: clause = "SAME " case netmap.Distinct: clause = "DISTINCT " default: clause = "" } _, err = w.WriteString(fmt.Sprintf(" IN %s%s", clause, s)) if err != nil { return err } } if s = p.selectors[i].GetFilter(); s != "" { _, err = w.WriteString(" FROM " + s) if err != nil { return err } } if s = p.selectors[i].GetName(); s != "" { _, err = w.WriteString(" AS " + s) if err != nil { return err } } } for i := range p.filters { _, err = w.WriteString("\nFILTER ") if err != nil { return err } err = writeFilterStringTo(w, p.filters[i], false) if err != nil { return err } } return nil } func writeFilterStringTo(w io.StringWriter, f netmap.Filter, mayNeedOuterBrackets bool) error { var err error var s string op := f.GetOp() unspecified := op == 0 if s = f.GetKey(); s != "" { _, err = w.WriteString(fmt.Sprintf("%s %s %s", escapeString(s), op, escapeString(f.GetValue()))) if err != nil { return err } } else if s = f.GetName(); unspecified && s != "" { _, err = w.WriteString(fmt.Sprintf("@%s", s)) if err != nil { return err } } inner := f.GetFilters() if op == netmap.NOT { _, err = w.WriteString(op.String() + " (") if err != nil { return err } err = writeFilterStringTo(w, inner[0], false) if err != nil { return err } _, err = w.WriteString(")") if err != nil { return err } } else { useBrackets := mayNeedOuterBrackets && op == netmap.OR && len(inner) > 1 if useBrackets { _, err = w.WriteString("(") if err != nil { return err } } for i := range inner { if i != 0 { _, err = w.WriteString(" " + op.String() + " ") if err != nil { return err } } err = writeFilterStringTo(w, inner[i], true) if err != nil { return err } } if useBrackets { _, err = w.WriteString(")") if err != nil { return err } } } if s = f.GetName(); s != "" && !unspecified { _, err = w.WriteString(" AS " + s) if err != nil { return err } } return nil } // DecodeString decodes PlacementPolicy from the string composed using // WriteStringTo. Returns error if s is malformed. func (p *PlacementPolicy) DecodeString(s string) error { var v policyVisitor input := antlr.NewInputStream(s) lexer := parser.NewQueryLexer(input) lexer.RemoveErrorListeners() lexer.AddErrorListener(&v) stream := antlr.NewCommonTokenStream(lexer, 0) pp := parser.NewQuery(stream) pp.BuildParseTrees = true pp.RemoveErrorListeners() pp.AddErrorListener(&v) pl := pp.Policy().Accept(&v) if len(v.errors) != 0 { return v.errors[0] } parsed, ok := pl.(*PlacementPolicy) if !ok { return fmt.Errorf("unexpected parsed instance type %T", pl) } else if parsed == nil { return errors.New("parsed nil value") } if err := validatePolicy(*parsed); err != nil { return fmt.Errorf("invalid policy: %w", err) } *p = *parsed return nil } // SelectFilterExpr is an expression containing only selectors and filters. // It's useful to evaluate their effect before being used in a policy. type SelectFilterExpr struct { cbf uint32 selector *netmap.Selector filters []netmap.Filter } // DecodeString decodes a string into a SelectFilterExpr. // Returns an error if s is malformed. func DecodeSelectFilterString(s string) (*SelectFilterExpr, error) { var v policyVisitor input := antlr.NewInputStream(s) lexer := parser.NewQueryLexer(input) lexer.RemoveErrorListeners() lexer.AddErrorListener(&v) stream := antlr.NewCommonTokenStream(lexer, 0) pp := parser.NewQuery(stream) pp.BuildParseTrees = true pp.RemoveErrorListeners() pp.AddErrorListener(&v) sfExpr := pp.SelectFilterExpr().Accept(&v) if len(v.errors) != 0 { return nil, v.errors[0] } parsed, ok := sfExpr.(*SelectFilterExpr) if !ok { return nil, fmt.Errorf("unexpected parsed instance type %T", sfExpr) } return parsed, nil } var ( // errUnknownFilter is returned when a value of FROM in a query is unknown. errUnknownFilter = errors.New("filter not found") // errUnknownSelector is returned when a value of IN is unknown. errUnknownSelector = errors.New("policy: selector not found") // errSyntaxError is returned for errors found by ANTLR parser. errSyntaxError = errors.New("policy: syntax error") // errRedundantSelector is returned for errors found by selectors policy validator. errRedundantSelector = errors.New("policy: found redundant selector") // errUnnamedSelector is returned for errors found by selectors policy validator. errUnnamedSelector = errors.New("policy: unnamed selectors are useless, " + "make sure to pair REP and SELECT clauses: \"REP .. IN X\" + \"SELECT ... AS X\"") // errRedundantSelector is returned for errors found by filters policy validator. errRedundantFilter = errors.New("policy: found redundant filter") // errECFewSelectors is returned when EC keyword is used without UNIQUE keyword. errECFewSelectors = errors.New("policy: too few nodes to select") ) type policyVisitor struct { errors []error parser.BaseQueryVisitor antlr.DefaultErrorListener } func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ any, line, column int, msg string, _ antlr.RecognitionException) { p.reportError(fmt.Errorf("%w: line %d:%d %s", errSyntaxError, line, column, msg)) } func (p *policyVisitor) reportError(err error) any { p.errors = append(p.errors, err) return nil } // VisitPolicy implements parser.QueryVisitor interface. func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) any { if len(p.errors) != 0 { return nil } pl := new(PlacementPolicy) pl.unique = ctx.UNIQUE() != nil stmts := ctx.GetChildren() for _, r := range stmts { var res *netmap.Replica var ok bool switch r := r.(type) { case parser.IRepStmtContext: res, ok = r.Accept(p).(*netmap.Replica) case parser.IEcStmtContext: res, ok = r.Accept(p).(*netmap.Replica) default: continue } if !ok { return nil } pl.replicas = append(pl.replicas, *res) } if cbfStmt := ctx.CbfStmt(); cbfStmt != nil { cbf, ok := cbfStmt.(*parser.CbfStmtContext).Accept(p).(uint32) if !ok { return nil } pl.SetContainerBackupFactor(cbf) } selStmts := ctx.AllSelectStmt() pl.selectors = make([]netmap.Selector, 0, len(selStmts)) for _, s := range selStmts { res, ok := s.Accept(p).(*netmap.Selector) if !ok { return nil } pl.selectors = append(pl.selectors, *res) } filtStmts := ctx.AllFilterStmt() pl.filters = make([]netmap.Filter, 0, len(filtStmts)) for _, f := range filtStmts { pl.filters = append(pl.filters, *f.Accept(p).(*netmap.Filter)) } return pl } func (p *policyVisitor) VisitSelectFilterExpr(ctx *parser.SelectFilterExprContext) any { if len(p.errors) != 0 { return nil } sfExpr := new(SelectFilterExpr) if cbfStmt := ctx.CbfStmt(); cbfStmt != nil { cbf, ok := cbfStmt.(*parser.CbfStmtContext).Accept(p).(uint32) if !ok { return nil } sfExpr.cbf = cbf } if selStmt := ctx.SelectStmt(); selStmt != nil { sel, ok := selStmt.Accept(p).(*netmap.Selector) if !ok { return nil } sfExpr.selector = sel } filtStmts := ctx.AllFilterStmt() sfExpr.filters = make([]netmap.Filter, 0, len(filtStmts)) for _, f := range filtStmts { sfExpr.filters = append(sfExpr.filters, *f.Accept(p).(*netmap.Filter)) } return sfExpr } func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) any { cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32) if err != nil { return p.reportError(errInvalidNumber) } return uint32(cbf) } // VisitRepStmt implements parser.QueryVisitor interface. func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) any { num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32) if err != nil { return p.reportError(errInvalidNumber) } rs := new(netmap.Replica) rs.SetCount(uint32(num)) if sel := ctx.GetSelector(); sel != nil { rs.SetSelector(sel.GetText()) } return rs } // VisitRepStmt implements parser.QueryVisitor interface. func (p *policyVisitor) VisitEcStmt(ctx *parser.EcStmtContext) any { dataCount, err := strconv.ParseUint(ctx.GetData().GetText(), 10, 32) if err != nil { return p.reportError(errInvalidNumber) } parityCount, err := strconv.ParseUint(ctx.GetParity().GetText(), 10, 32) if err != nil { return p.reportError(errInvalidNumber) } rs := new(netmap.Replica) rs.SetECDataCount(uint32(dataCount)) rs.SetECParityCount(uint32(parityCount)) if sel := ctx.GetSelector(); sel != nil { rs.SetSelector(sel.GetText()) } return rs } // VisitSelectStmt implements parser.QueryVisitor interface. func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) any { res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32) if err != nil { return p.reportError(errInvalidNumber) } s := new(netmap.Selector) s.SetCount(uint32(res)) if clStmt := ctx.Clause(); clStmt != nil { s.SetClause(clauseFromString(clStmt.GetText())) } if bStmt := ctx.GetBucket(); bStmt != nil { s.SetAttribute(ctx.GetBucket().GetText()) } s.SetFilter(ctx.GetFilter().GetText()) // either ident or wildcard if ctx.AS() != nil { s.SetName(ctx.GetName().GetText()) } return s } // VisitFilterStmt implements parser.QueryVisitor interface. func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) any { f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter) f.SetName(ctx.GetName().GetText()) return f } func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) any { if eCtx := ctx.Expr(); eCtx != nil { return eCtx.Accept(p) } if inner := ctx.GetInner(); inner != nil { return inner.Accept(p) } f := new(netmap.Filter) op := operationFromString(ctx.GetOp().GetText()) f.SetOp(op) if op == netmap.NOT { f1 := *ctx.GetF1().Accept(p).(*netmap.Filter) f.SetFilters([]netmap.Filter{f1}) return f } f1 := *ctx.GetF1().Accept(p).(*netmap.Filter) f2 := *ctx.GetF2().Accept(p).(*netmap.Filter) // Consider f1=(.. AND ..) AND f2. This can be merged because our AND operation // is of arbitrary arity. ANTLR generates left-associative parse-tree by default. if f1.GetOp() == op { f.SetFilters(append(f1.GetFilters(), f2)) return f } f.SetFilters([]netmap.Filter{f1, f2}) return f } // VisitFilterKey implements parser.QueryVisitor interface. func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) any { if id := ctx.Ident(); id != nil { return id.GetText() } str := ctx.STRING().GetText() return str[1 : len(str)-1] } func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) any { if id := ctx.Ident(); id != nil { return id.GetText() } if num := ctx.Number(); num != nil { return num.GetText() } str := ctx.STRING().GetText() return str[1 : len(str)-1] } // VisitExpr implements parser.QueryVisitor interface. func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any { f := new(netmap.Filter) if flt := ctx.GetFilter(); flt != nil { f.SetName(flt.GetText()) return f } key := ctx.GetKey().Accept(p) opStr := ctx.SIMPLE_OP().GetText() value := ctx.GetValue().Accept(p) f.SetKey(key.(string)) f.SetOp(operationFromString(opStr)) f.SetValue(value.(string)) return f } // validatePolicy checks high-level constraints such as filter link in SELECT // being actually defined in FILTER section. func validatePolicy(p PlacementPolicy) error { canOmitNames := len(p.selectors) == 1 && len(p.replicas) == 1 seenFilters := map[string]bool{} expectedFilters := map[string]struct{}{} for i := range p.filters { seenFilters[p.filters[i].GetName()] = true for _, f := range p.filters[i].GetFilters() { if f.GetName() != "" { expectedFilters[f.GetName()] = struct{}{} } } } seenSelectors := map[string]*netmap.Selector{} for i := range p.selectors { if p.selectors[i].GetName() == "" && !canOmitNames { return errUnnamedSelector } if flt := p.selectors[i].GetFilter(); flt != mainFilterName { expectedFilters[flt] = struct{}{} if !seenFilters[flt] { return fmt.Errorf("%w: '%s'", errUnknownFilter, flt) } } seenSelectors[p.selectors[i].GetName()] = &p.selectors[i] } for _, f := range p.filters { if _, ok := expectedFilters[f.GetName()]; !ok { return fmt.Errorf("%w: '%s'", errRedundantFilter, f.GetName()) } } expectedSelectors := map[string]struct{}{} for i := range p.replicas { selName := p.replicas[i].GetSelector() if selName != "" || canOmitNames { expectedSelectors[selName] = struct{}{} if seenSelectors[selName] == nil { return fmt.Errorf("%w: '%s'", errUnknownSelector, selName) } dataCount := p.replicas[i].GetECDataCount() parityCount := p.replicas[i].GetECParityCount() if dataCount != 0 || parityCount != 0 { if c := seenSelectors[selName].GetCount(); c < dataCount+parityCount { return fmt.Errorf("%w: %d < %d + %d", errECFewSelectors, c, dataCount, parityCount) } } } } for _, s := range p.selectors { if _, ok := expectedSelectors[s.GetName()]; !ok { return fmt.Errorf("%w: to use selector '%s' use keyword IN", errRedundantSelector, s.GetName()) } } return nil } func clauseFromString(s string) (c netmap.Clause) { if !c.FromString(strings.ToUpper(s)) { // Such errors should be handled by ANTLR code thus this panic. panic(fmt.Errorf("BUG: invalid clause: %s", c)) } return } func operationFromString(s string) (op netmap.Operation) { if !op.FromString(strings.ToUpper(s)) { // Such errors should be handled by ANTLR code thus this panic. panic(fmt.Errorf("BUG: invalid operation: %s", op)) } return } // escapeString returns single quote wrapped string. // Wrapping rules must be kept in sync with QueryLexer.g4. // Currently only ASCII letters, digits and underscore can be parsed without quotes. func escapeString(s string) string { for _, r := range s { if 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || '0' <= r && r <= '9' || r == '_' { continue } return "'" + s + "'" } return s }