Allow to parse single unnamed selectors #162
5 changed files with 63 additions and 8 deletions
|
@ -131,7 +131,8 @@ Its basic syntax is as follows:
|
||||||
REP <count> {IN <select>}
|
REP <count> {IN <select>}
|
||||||
```
|
```
|
||||||
|
|
||||||
If a select is not specified, then the entire netmap is used as input. The resulting nodes will be used to actually store objects and they constitute a replica group (or simply, "a replica").
|
If a select is not specified, then the entire netmap is used as input. The only exception to this rule is when exactly 1 replica and 1 selector are being present: in this case the only selector is being used instead of the whole netmap.
|
||||||
|
The resulting nodes will be used to actually store objects and they constitute a replica group (or simply, "a replica").
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
```sql
|
```sql
|
||||||
|
|
|
@ -238,7 +238,7 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e
|
||||||
// marked as used by earlier replicas.
|
// marked as used by earlier replicas.
|
||||||
for i := range p.replicas {
|
for i := range p.replicas {
|
||||||
sName := p.replicas[i].GetSelector()
|
sName := p.replicas[i].GetSelector()
|
||||||
if sName == "" {
|
if sName == "" && !(len(p.replicas) == 1 && len(p.selectors) == 1) {
|
||||||
var s netmap.Selector
|
var s netmap.Selector
|
||||||
s.SetCount(p.replicas[i].GetCount())
|
s.SetCount(p.replicas[i].GetCount())
|
||||||
s.SetFilter(mainFilterName)
|
s.SetFilter(mainFilterName)
|
||||||
|
|
|
@ -852,6 +852,7 @@ func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any {
|
||||||
// validatePolicy checks high-level constraints such as filter link in SELECT
|
// validatePolicy checks high-level constraints such as filter link in SELECT
|
||||||
// being actually defined in FILTER section.
|
// being actually defined in FILTER section.
|
||||||
func validatePolicy(p PlacementPolicy) error {
|
func validatePolicy(p PlacementPolicy) error {
|
||||||
|
canOmitNames := len(p.selectors) == 1 && len(p.replicas) == 1
|
||||||
seenFilters := map[string]bool{}
|
seenFilters := map[string]bool{}
|
||||||
expectedFilters := map[string]struct{}{}
|
expectedFilters := map[string]struct{}{}
|
||||||
for i := range p.filters {
|
for i := range p.filters {
|
||||||
|
@ -865,7 +866,7 @@ func validatePolicy(p PlacementPolicy) error {
|
||||||
|
|
||||||
seenSelectors := map[string]*netmap.Selector{}
|
seenSelectors := map[string]*netmap.Selector{}
|
||||||
for i := range p.selectors {
|
for i := range p.selectors {
|
||||||
if p.selectors[i].GetName() == "" {
|
if p.selectors[i].GetName() == "" && !canOmitNames {
|
||||||
return errUnnamedSelector
|
return errUnnamedSelector
|
||||||
}
|
}
|
||||||
if flt := p.selectors[i].GetFilter(); flt != mainFilterName {
|
if flt := p.selectors[i].GetFilter(); flt != mainFilterName {
|
||||||
|
@ -886,7 +887,7 @@ func validatePolicy(p PlacementPolicy) error {
|
||||||
expectedSelectors := map[string]struct{}{}
|
expectedSelectors := map[string]struct{}{}
|
||||||
for i := range p.replicas {
|
for i := range p.replicas {
|
||||||
selName := p.replicas[i].GetSelector()
|
selName := p.replicas[i].GetSelector()
|
||||||
if selName != "" {
|
if selName != "" || canOmitNames {
|
||||||
expectedSelectors[selName] = struct{}{}
|
expectedSelectors[selName] = struct{}{}
|
||||||
if seenSelectors[selName] == nil {
|
if seenSelectors[selName] == nil {
|
||||||
return fmt.Errorf("%w: '%s'", errUnknownSelector, selName)
|
return fmt.Errorf("%w: '%s'", errUnknownSelector, selName)
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
|
|
||||||
func TestDecodeString(t *testing.T) {
|
func TestDecodeString(t *testing.T) {
|
||||||
testCases := []string{
|
testCases := []string{
|
||||||
|
`REP 2
|
||||||
|
CBF 2
|
||||||
|
SELECT 2 FROM *`,
|
||||||
`REP 1 IN X
|
`REP 1 IN X
|
||||||
CBF 1
|
CBF 1
|
||||||
SELECT 2 IN SAME Location FROM * AS X`,
|
SELECT 2 IN SAME Location FROM * AS X`,
|
||||||
|
@ -48,10 +51,11 @@ REP 1`,
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidTestCases := map[string]error{
|
invalidTestCases := map[string]error{
|
||||||
`?REP 1`: errSyntaxError,
|
`?REP 1`: errSyntaxError,
|
||||||
`REP 1 trailing garbage`: errSyntaxError,
|
`REP 1 trailing garbage`: errSyntaxError,
|
||||||
`REP 1 SELECT 4 FROM * `: errUnnamedSelector,
|
`REP 1 REP 1 SELECT 4 FROM *`: errUnnamedSelector,
|
||||||
`REP 1 SELECT 4 FROM * AS X`: errRedundantSelector,
|
`REP 1 SELECT 4 FROM * SELECT 1 FROM *`: errUnnamedSelector,
|
||||||
|
`REP 1 IN X SELECT 4 FROM *`: errUnknownSelector,
|
||||||
`REP 1 IN X REP 2 SELECT 4 FROM * AS X FILTER 'UN-LOCODE' EQ 'RU LED' AS F`: errRedundantFilter,
|
`REP 1 IN X REP 2 SELECT 4 FROM * AS X FILTER 'UN-LOCODE' EQ 'RU LED' AS F`: errRedundantFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -283,6 +283,55 @@ func TestPlacementPolicy_Unique(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlacementPolicy_SingleOmitNames(t *testing.T) {
|
||||||
|
nodes := []NodeInfo{
|
||||||
|
nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
|
||||||
|
nodeInfoFromAttributes("ID", "2", "Country", "Germany", "City", "Berlin"),
|
||||||
|
nodeInfoFromAttributes("ID", "3", "Country", "Russia", "City", "Moscow"),
|
||||||
|
nodeInfoFromAttributes("ID", "4", "Country", "France", "City", "Paris"),
|
||||||
|
nodeInfoFromAttributes("ID", "5", "Country", "France", "City", "Lyon"),
|
||||||
|
nodeInfoFromAttributes("ID", "6", "Country", "Russia", "City", "SPB"),
|
||||||
|
nodeInfoFromAttributes("ID", "7", "Country", "Russia", "City", "Moscow"),
|
||||||
|
nodeInfoFromAttributes("ID", "8", "Country", "Germany", "City", "Darmstadt"),
|
||||||
|
nodeInfoFromAttributes("ID", "9", "Country", "Germany", "City", "Frankfurt"),
|
||||||
|
nodeInfoFromAttributes("ID", "10", "Country", "Russia", "City", "SPB"),
|
||||||
|
nodeInfoFromAttributes("ID", "11", "Country", "Russia", "City", "Moscow"),
|
||||||
|
nodeInfoFromAttributes("ID", "12", "Country", "Germany", "City", "London"),
|
||||||
|
}
|
||||||
|
for i := range nodes {
|
||||||
|
pub := make([]byte, 33)
|
||||||
|
rand.Read(pub)
|
||||||
|
nodes[i].SetPublicKey(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nm NetMap
|
||||||
|
nm.SetNodes(nodes)
|
||||||
|
|
||||||
|
for _, unique := range []bool{false, true} {
|
||||||
|
t.Run(fmt.Sprintf("unique=%t", unique), func(t *testing.T) {
|
||||||
|
ssNamed := []Selector{newSelector("X", "City", 2, "FromRU", (*Selector).SelectDistinct)}
|
||||||
|
fsNamed := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
|
||||||
|
rsNamed := []ReplicaDescriptor{newReplica(1, "X")}
|
||||||
|
pNamed := newPlacementPolicy(3, rsNamed, ssNamed, fsNamed)
|
||||||
|
pNamed.unique = unique
|
||||||
|
|
||||||
|
vNamed, err := nm.ContainerNodes(pNamed, []byte{1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ssUnnamed := []Selector{newSelector("", "City", 2, "FromRU", (*Selector).SelectDistinct)}
|
||||||
|
fsUnnamed := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
|
||||||
|
rsUnnamed := []ReplicaDescriptor{newReplica(1, "")}
|
||||||
|
pUnnamed := newPlacementPolicy(3, rsUnnamed, ssUnnamed, fsUnnamed)
|
||||||
|
pUnnamed.unique = unique
|
||||||
|
|
||||||
|
vUnnamed, err := nm.ContainerNodes(pUnnamed, []byte{1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, vNamed, vUnnamed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPlacementPolicy_MultiREP(t *testing.T) {
|
func TestPlacementPolicy_MultiREP(t *testing.T) {
|
||||||
nodes := []NodeInfo{
|
nodes := []NodeInfo{
|
||||||
nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
|
nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
|
||||||
|
|
Loading…
Reference in a new issue