// Copyright 2017 Pilosa Corp. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pilosa import ( "encoding/json" "fmt" "io/ioutil" "strings" "testing" "github.com/pilosa/pilosa/v2/pql" ) func TestExecutor_TranslateGroupByCall(t *testing.T) { holder := NewHolder(DefaultPartitionN) cluster := newCluster() cluster.holder = holder cluster.Node = &Node{ID: "node1", URI: NewTestURIFromHostPort("node1", 0)} if err := cluster.addNode(cluster.Node); err != nil { t.Fatal(err) } e := &executor{ Holder: holder, Cluster: cluster, } e.Holder.Path, _ = ioutil.TempDir(*TempDir, "") err := e.Holder.Open() if err != nil { t.Fatalf("opening holder: %v", err) } idx, err := e.Holder.CreateIndex("i", IndexOptions{}) if err != nil { t.Fatalf("creating index: %v", err) } _, erra := idx.CreateField("ak", OptFieldKeys()) _, errb := idx.CreateField("b") _, errc := idx.CreateField("ck", OptFieldKeys()) if erra != nil || errb != nil || errc != nil { t.Fatalf("creating fields %v, %v, %v", erra, errb, errc) } query, err := pql.ParseString(`GroupBy(Rows(ak), Rows(b), Rows(ck), previous=["la", 0, "ha"], having=Condition(count > 10))`) if err != nil { t.Fatalf("parsing query: %v", err) } c := query.Calls[0] err = e.translateGroupByCall("i", idx, true, c, make(map[string]uint64)) if err != nil { t.Fatalf("translating call: %v", err) } if len(c.Args["previous"].([]interface{})) != 3 { t.Fatalf("unexpected length for 'previous' arg %v", c.Args["previous"]) } for i, v := range c.Args["previous"].([]interface{}) { if !isInt(v) { t.Fatalf("expected all items in previous to be ints, but '%v' at index %d is %[1]T", v, i) } } if having, hok := c.Args["having"].(*pql.Call); !hok { t.Fatal("expected having to be a call") } else if cond, cok := having.Args["count"].(*pql.Condition); !cok { t.Fatal("expected condition to be a count") } else if cond.Op != pql.GT { t.Fatal("expected condition op to be >") } else { val, ok := cond.Uint64Value() if !ok || val != uint64(10) { t.Fatal("expected condition val to be uint64(10)") } } errTests := []struct { pql string err string }{ { pql: `GroupBy(Rows(notfound), previous=1)`, err: "'previous' argument must be list", }, { pql: `GroupBy(Rows(ak), previous=["la", 0])`, err: "mismatched lengths", }, { pql: `GroupBy(Rows(ak), previous=[1])`, err: "prev value must be a string", }, { pql: `GroupBy(Rows(notfound), previous=[1])`, err: ErrFieldNotFound.Error(), }, // TODO: an unknown key will actually allocate an id. this is probably bad. // { // pql: `GroupBy(Rows(ak), previous=["zoop"])`, // err: "translating row key '", // }, { pql: `GroupBy(Rows(b), previous=["la"])`, err: "which doesn't use string keys", }, } for i, test := range errTests { t.Run(fmt.Sprintf("#%d_%s", i, test.err), func(t *testing.T) { query, err := pql.ParseString(test.pql) if err != nil { t.Fatalf("parsing query: %v", err) } c := query.Calls[0] err = e.translateGroupByCall("i", idx, true, c, make(map[string]uint64)) if err == nil { t.Fatalf("expected error, but translated call is '%s", c) } if !strings.Contains(err.Error(), test.err) { t.Fatalf("expected '%s', got '%v'", test.err, err) } }) } } func isInt(a interface{}) bool { switch a.(type) { case int, int64, uint, uint64: return true default: return false } } func TestFilterWithLimit(t *testing.T) { f := filterWithLimit(5) for i := uint64(0); i < 5; i++ { include, done := f(i, i*(1< 99", exp: true}, {cond: "count >= 99", exp: true}, {cond: "count == 100", exp: true}, {cond: "count != 100", exp: false}, {cond: "count < 100", exp: false}, {cond: "count <= 100", exp: true}, {cond: "count > 100", exp: false}, {cond: "count >= 100", exp: true}, {cond: "count == 101", exp: false}, {cond: "count != 101", exp: true}, {cond: "count < 101", exp: true}, {cond: "count <= 101", exp: true}, {cond: "count > 101", exp: false}, {cond: "count >= 101", exp: false}, {cond: "98 < count < 100", exp: false}, {cond: "98 < count <= 100", exp: true}, {cond: "98 < count < 101", exp: true}, {cond: "100 <= count < 102", exp: true}, {cond: "100 < count < 102", exp: false}, {cond: "98 <= count <= 102", exp: true}, }, }, { groupCount: GroupCount{Sum: 100}, checks: []condCheck{ {cond: "sum == 99", exp: false}, {cond: "sum != 99", exp: true}, {cond: "sum < 99", exp: false}, {cond: "sum <= 99", exp: false}, {cond: "sum > 99", exp: true}, {cond: "sum >= 99", exp: true}, {cond: "sum == 100", exp: true}, {cond: "sum != 100", exp: false}, {cond: "sum < 100", exp: false}, {cond: "sum <= 100", exp: true}, {cond: "sum > 100", exp: false}, {cond: "sum >= 100", exp: true}, {cond: "sum == 101", exp: false}, {cond: "sum != 101", exp: true}, {cond: "sum < 101", exp: true}, {cond: "sum <= 101", exp: true}, {cond: "sum > 101", exp: false}, {cond: "sum >= 101", exp: false}, {cond: "98 < sum < 100", exp: false}, {cond: "98 < sum <= 100", exp: true}, {cond: "98 < sum < 101", exp: true}, {cond: "100 <= sum < 102", exp: true}, {cond: "100 < sum < 102", exp: false}, {cond: "98 <= sum <= 102", exp: true}, }, }, { groupCount: GroupCount{Sum: -100}, checks: []condCheck{ {cond: "sum == -99", exp: false}, {cond: "sum != -99", exp: true}, {cond: "sum < -99", exp: true}, {cond: "sum <= -99", exp: true}, {cond: "sum > -99", exp: false}, {cond: "sum >= -99", exp: false}, {cond: "sum == -100", exp: true}, {cond: "sum != -100", exp: false}, {cond: "sum < -100", exp: false}, {cond: "sum <= -100", exp: true}, {cond: "sum > -100", exp: false}, {cond: "sum >= -100", exp: true}, {cond: "sum == -101", exp: false}, {cond: "sum != -101", exp: true}, {cond: "sum < -101", exp: false}, {cond: "sum <= -101", exp: false}, {cond: "sum > -101", exp: true}, {cond: "sum >= -101", exp: true}, {cond: "-100 < sum < -98", exp: false}, {cond: "-100 <= sum < -98", exp: true}, {cond: "-101 < sum < -98", exp: true}, {cond: "-102 < sum <= -100", exp: true}, {cond: "-102 < sum < -100", exp: false}, {cond: "-102 <= sum <= -98", exp: true}, }, }, } for i, test := range tests { t.Run(fmt.Sprintf("test (#%d):", i), func(t *testing.T) { for j, check := range test.checks { t.Run(fmt.Sprintf("check (#%d):", j), func(t *testing.T) { query, err := pql.ParseString(fmt.Sprintf("GroupBy(Rows(a), having=Condition(%s))", check.cond)) if err != nil { t.Fatalf("parsing query: %v", err) } c := query.Calls[0] having := c.Args["having"].(*pql.Call) var got bool for subj, cond := range having.Args { switch subj { case "count", "sum": condition, ok := cond.(*pql.Condition) if !ok { t.Fatalf("not a valid condition") } got = test.groupCount.satisfiesCondition(subj, condition) } } if got != check.exp { t.Fatalf("expected: %v, but got: %v", check.exp, got) } }) } }) } }) }