1
0
Fork 0
golang-github-blevesearch-b.../search/query/query_test.go
Daniel Baumann 982828099e
Adding upstream version 2.5.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-19 00:20:02 +02:00

1042 lines
22 KiB
Go

// Copyright (c) 2014 Couchbase, Inc.
//
// 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 query
import (
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/blevesearch/bleve/v2/geo"
"github.com/blevesearch/bleve/v2/mapping"
)
var minNum = 5.1
var maxNum = 7.1
var minTerm = "bob"
var maxTerm = "cat"
var startDateStr = "2011-01-01T00:00:00Z"
var endDateStr = "2012-01-01T00:00:00Z"
var startDate time.Time
var endDate time.Time
func init() {
var err error
startDate, err = time.Parse(time.RFC3339, startDateStr)
if err != nil {
panic(err)
}
endDate, err = time.Parse(time.RFC3339, endDateStr)
if err != nil {
panic(err)
}
}
func TestParseQuery(t *testing.T) {
tests := []struct {
input []byte
output Query
err bool
}{
{
input: []byte(`{"term":"water","field":"desc"}`),
output: func() Query {
q := NewTermQuery("water")
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc"}`),
output: func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
output: func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"and"}`),
output: func() Query {
q := NewMatchQuery("beer")
q.SetOperator(MatchQueryOperatorAnd)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"and"}`),
output: func() Query {
operator := MatchQueryOperatorAnd
q := NewMatchQuery("beer")
q.SetOperator(operator)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
output: func() Query {
q := NewMatchQuery("beer")
q.SetOperator(MatchQueryOperatorOr)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
output: func() Query {
operator := MatchQueryOperatorOr
q := NewMatchQuery("beer")
q.SetOperator(operator)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match":"beer","field":"desc","operator":"does not exist"}`),
output: nil,
err: true,
},
{
input: []byte(`{"match_phrase":"light beer","field":"desc"}`),
output: func() Query {
q := NewMatchPhraseQuery("light beer")
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"must":{"conjuncts": [{"match":"beer","field":"desc"}]},"should":{"disjuncts": [{"match":"water","field":"desc"}],"min":1.0},"must_not":{"disjuncts": [{"match":"devon","field":"desc"}]}}`),
output: func() Query {
q := NewBooleanQuery(
[]Query{func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("water")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("devon")
q.SetField("desc")
return q
}()})
q.SetMinShould(1)
return q
}(),
},
{
input: []byte(`{"terms":["watered","down"],"field":"desc"}`),
output: NewPhraseQuery([]string{"watered", "down"}, "desc"),
},
{
input: []byte(`{"query":"+beer \"light beer\" -devon"}`),
output: NewQueryStringQuery(`+beer "light beer" -devon`),
},
{
input: []byte(`{"min":5.1,"max":7.1,"field":"desc"}`),
output: func() Query {
q := NewNumericRangeQuery(&minNum, &maxNum)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"min":"bob","max":"cat","field":"desc"}`),
output: func() Query {
q := NewTermRangeQuery(minTerm, maxTerm)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"start":"` + startDateStr + `","end":"` + endDateStr + `","field":"desc"}`),
output: func() Query {
q := NewDateRangeStringQuery(startDateStr, endDateStr)
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"prefix":"budwei","field":"desc"}`),
output: func() Query {
q := NewPrefixQuery("budwei")
q.SetField("desc")
return q
}(),
},
{
input: []byte(`{"match_all":{}}`),
output: NewMatchAllQuery(),
},
{
input: []byte(`{"match_none":{}}`),
output: NewMatchNoneQuery(),
},
{
input: []byte(`{"ids":["a","b","c"]}`),
output: NewDocIDQuery([]string{"a", "b", "c"}),
},
{
input: []byte(`{"bool": true}`),
output: NewBoolFieldQuery(true),
},
{
input: []byte(`{"field": "x", "cidr": "1.2.3.0/4"}`),
output: func() Query {
q := NewIPRangeQuery("1.2.3.0/4")
q.SetField("x")
return q
}(),
},
{
input: []byte(`{"madeitup":"queryhere"}`),
output: nil,
err: true,
},
}
for i, test := range tests {
actual, err := ParseQuery(test.input)
if err != nil && test.err == false {
t.Errorf("error %v for %d", err, i)
}
if !reflect.DeepEqual(test.output, actual) {
t.Errorf("expected: %#v, got: %#v for %s", test.output, actual, string(test.input))
}
}
}
func TestQueryValidate(t *testing.T) {
tests := []struct {
query Query
err bool
}{
{
query: func() Query {
q := NewTermQuery("water")
q.SetField("desc")
return q
}(),
},
{
query: func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}(),
},
{
query: func() Query {
q := NewMatchPhraseQuery("light beer")
q.SetField("desc")
return q
}(),
},
{
query: func() Query {
q := NewNumericRangeQuery(&minNum, &maxNum)
q.SetField("desc")
return q
}(),
},
{
query: func() Query {
q := NewNumericRangeQuery(nil, nil)
q.SetField("desc")
return q
}(),
err: true,
},
{
query: func() Query {
q := NewDateRangeQuery(startDate, endDate)
q.SetField("desc")
return q
}(),
},
{
query: func() Query {
q := NewPrefixQuery("budwei")
q.SetField("desc")
return q
}(),
},
{
query: NewQueryStringQuery(`+beer "light beer" -devon`),
},
{
query: NewPhraseQuery([]string{"watered", "down"}, "desc"),
},
{
query: NewPhraseQuery([]string{}, "field"),
err: true,
},
{
query: func() Query {
q := NewMatchNoneQuery()
q.SetBoost(25)
return q
}(),
},
{
query: func() Query {
q := NewMatchAllQuery()
q.SetBoost(25)
return q
}(),
},
{
query: NewBooleanQuery(
[]Query{func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("water")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("devon")
q.SetField("desc")
return q
}()}),
},
{
query: NewBooleanQuery(
nil,
nil,
[]Query{func() Query {
q := NewMatchQuery("devon")
q.SetField("desc")
return q
}()}),
},
{
query: NewBooleanQuery(
[]Query{},
[]Query{},
[]Query{func() Query {
q := NewMatchQuery("devon")
q.SetField("desc")
return q
}()}),
},
{
query: NewBooleanQuery(
nil,
nil,
nil),
err: true,
},
{
query: NewBooleanQuery(
[]Query{},
[]Query{},
[]Query{}),
err: true,
},
{
query: func() Query {
q := NewBooleanQuery(
[]Query{func() Query {
q := NewMatchQuery("beer")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("water")
q.SetField("desc")
return q
}()},
[]Query{func() Query {
q := NewMatchQuery("devon")
q.SetField("desc")
return q
}()})
q.SetMinShould(2)
return q
}(),
err: true,
},
{
query: func() Query {
q := NewDocIDQuery(nil)
q.SetBoost(25)
return q
}(),
},
}
for _, test := range tests {
if vq, ok := test.query.(ValidatableQuery); ok {
actual := vq.Validate()
if actual != nil && !test.err {
t.Errorf("expected no error: %#v got %#v", test.err, actual)
} else if actual == nil && test.err {
t.Errorf("expected error: %#v got %#v", test.err, actual)
}
}
}
}
func TestDumpQuery(t *testing.T) {
mapping := mapping.NewIndexMapping()
q := NewQueryStringQuery("+water -light beer")
s, err := DumpQuery(mapping, q)
if err != nil {
t.Fatal(err)
}
s = strings.TrimSpace(s)
wanted := strings.TrimSpace(`{
"must": {
"conjuncts": [
{
"match": "water",
"prefix_length": 0,
"fuzziness": 0
}
]
},
"should": {
"disjuncts": [
{
"match": "beer",
"prefix_length": 0,
"fuzziness": 0
}
],
"min": 0
},
"must_not": {
"disjuncts": [
{
"match": "light",
"prefix_length": 0,
"fuzziness": 0
}
],
"min": 0
}
}`)
if wanted != s {
t.Fatalf("query:\n%s\ndiffers from expected:\n%s", s, wanted)
}
}
func TestGeoShapeQuery(t *testing.T) {
tests := []struct {
input []byte
output Query
err bool
}{
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "polygon",
"coordinates": [[
[
74.1357421875,
30.600093873550072
],
[
67.0166015625,
21.57571893245848
],
[
68.8623046875,
9.145486056167277
],
[
83.1884765625,
4.083452772038619
],
[
88.9892578125,
22.67484735118852
],
[
74.1357421875,
30.600093873550072
]]]
},
"relation": "intersects"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{{{{74.1357421875, 30.600093873550072},
{67.0166015625, 21.57571893245848}, {68.8623046875, 9.145486056167277},
{83.1884765625, 4.083452772038619}, {88.9892578125, 22.67484735118852},
{74.1357421875, 30.600093873550072}}}}, geo.PolygonType, "intersects")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "multipolygon",
"coordinates": [
[[
[
77.58268117904663,
12.980513152175025
],
[
77.58147954940794,
12.977983107483992
],
[
77.58708000183104,
12.97886130773254
],
[
77.58268117904663,
12.980513152175025
]
]],
[[
[
77.5864577293396,
12.97762764459667
],
[
77.58879661560059,
12.975076660730531
],
[
77.59115695953369,
12.979216768855913
],
[
77.5864577293396,
12.97762764459667
]
]]
]
},
"relation": "contains"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{
{{{77.58268117904663, 12.980513152175025},
{77.58147954940794, 12.977983107483992}, {77.58708000183104, 12.97886130773254},
{77.58268117904663, 12.980513152175025}}},
{{{77.5864577293396, 12.97762764459667}, {77.58879661560059, 12.975076660730531},
{77.59115695953369, 12.979216768855913}, {77.5864577293396, 12.97762764459667}}}},
geo.MultiPolygonType, "contains")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "point",
"coordinates": [77.58268117904663, 12.980513152175025]
},
"relation": "contains"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{
{{{77.58268117904663, 12.980513152175025}}}},
geo.PointType, "contains")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "multipoint",
"coordinates": [[77.58268117904663, 12.980513152175025],
[77.5864577293396, 12.97762764459667]]
},
"relation": "intersects"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{
{{{77.58268117904663, 12.980513152175025},
{77.5864577293396, 12.97762764459667}}}},
geo.MultiPointType, "intersects")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "linestring",
"coordinates": [[77.58268117904663, 12.980513152175025],
[77.5864577293396, 12.97762764459667]]
},
"relation": "intersects"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{
{{{77.58268117904663, 12.980513152175025},
{77.5864577293396, 12.97762764459667}}}},
geo.LineStringType, "intersects")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "multilinestring",
"coordinates": [
[[77.58268117904663, 12.980513152175025],
[77.5864577293396, 12.97762764459667]],
[[77.5864577293396,12.97762764459667],
[77.58879661560059, 12.975076660730531]]]
},
"relation": "intersects"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{{
{{77.58268117904663, 12.980513152175025},
{77.5864577293396, 12.97762764459667}},
{{77.5864577293396, 12.97762764459667},
{77.58879661560059, 12.975076660730531}}}},
geo.MultiLineStringType, "intersects")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "envelope",
"coordinates": [[77.58268117904663, 12.980513152175025],
[77.5864577293396, 12.97762764459667]]
},
"relation": "within"
}}`),
output: func() Query {
q, _ := NewGeoShapeQuery([][][][]float64{{
{{77.58268117904663, 12.980513152175025},
{77.5864577293396, 12.97762764459667}}}},
geo.EnvelopeType, "within")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "circle",
"coordinates": [77.58268117904663, 12.980513152175025],
"radius": "100m"
},
"relation": "within"
}}`),
output: func() Query {
q, _ := NewGeoShapeCircleQuery([]float64{
77.58268117904663, 12.980513152175025},
"100m", "within")
q.SetField("region")
return q
}(),
},
{
input: []byte(`{
"field" : "region",
"geometry": {
"shape": {
"type": "geometrycollection",
"geometries": [
{
"type": "point",
"coordinates": [
77.59158611297607,
12.972002899506203
]
},
{
"type": "linestring",
"coordinates": [
[
77.58851766586304,
12.973152950670608
],
[
77.58937597274779,
12.972212000113458
]
]
},
{
"type": "polygon",
"coordinates": [
[
[
77.59055614471436,
12.974721193688106
],
[
77.58954763412476,
12.97350841995465
],
[
77.59141445159912,
12.973382960265356
],
[
77.59055614471436,
12.974721193688106
]
]
]
}
]
},
"relation": "contains"
}}`),
output: func() Query {
q, _ := NewGeometryCollectionQuery([][][][][]float64{
{{{{77.59158611297607, 12.972002899506203}}}},
{{{{77.58851766586304, 12.973152950670608}, {77.58937597274779, 12.972212000113458}}}},
{{{{77.59055614471436, 12.974721193688106}, {77.58954763412476, 12.97350841995465},
{77.59141445159912, 12.973382960265356}, {77.59055614471436, 12.974721193688106}}}},
},
[]string{"point", "linestring", "polygon"}, "contains")
q.SetField("region")
return q
}(),
},
}
for i, test := range tests {
actual, err := ParseQuery(test.input)
if err != nil && test.err == false {
t.Errorf("error %v for %d", err, i)
}
if !reflect.DeepEqual(test.output, actual) {
t.Errorf("expected: %#v, got: %#v for %s", test.output, actual, string(test.input))
}
}
}
func TestParseEmptyQuery(t *testing.T) {
var qBytes []byte
rv, err := ParseQuery(qBytes)
if err != nil {
t.Fatal(err)
}
expect := NewMatchNoneQuery()
if !reflect.DeepEqual(rv, expect) {
t.Errorf("[1] Expected %#v, got %#v", expect, rv)
}
qBytes = []byte(`{}`)
rv, err = ParseQuery(qBytes)
if err != nil {
t.Fatal(err)
}
expect = NewMatchNoneQuery()
if !reflect.DeepEqual(rv, expect) {
t.Errorf("[2] Expected %#v, got %#v", expect, rv)
}
}
func TestExtractFields(t *testing.T) {
testQueries := []struct {
query string
expFields []string
}{
{
query: `{"term":"water","field":"desc"}`,
expFields: []string{"desc"},
},
{
query: `{
"must": {
"conjuncts": [
{
"match": "water",
"prefix_length": 0,
"fuzziness": 0
}
]
},
"should": {
"disjuncts": [
{
"match": "beer",
"prefix_length": 0,
"fuzziness": 0
}
],
"min": 0
},
"must_not": {
"disjuncts": [
{
"match": "light",
"prefix_length": 0,
"fuzziness": 0
}
],
"min": 0
}
}`,
expFields: []string{"_all"},
},
{
query: `{
"must": {
"conjuncts": [
{
"match": "water",
"prefix_length": 0,
"field": "desc",
"fuzziness": 0
}
]
},
"should": {
"disjuncts": [
{
"match": "beer",
"prefix_length": 0,
"field": "desc",
"fuzziness": 0
}
],
"min": 0
},
"must_not": {
"disjuncts": [
{
"match": "light",
"prefix_length": 0,
"field": "genre",
"fuzziness": 0
}
],
"min": 0
}
}`,
expFields: []string{"desc", "genre"},
},
{
query: `
{
"conjuncts": [
{
"conjuncts": [
{
"conjuncts": [
{
"conjuncts": [
{
"field": "date",
"start": "2002-09-05T08:09:00Z",
"end": "2007-03-01T03:52:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number",
"min": 1260295,
"max": 3917314,
"inclusive_min": true,
"inclusive_max": true
}
]
},
{
"conjuncts": [
{
"field": "date2",
"start": "2004-08-21T18:30:00Z",
"end": "2006-03-24T08:08:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number",
"min": 165449,
"max": 3847517,
"inclusive_min": true,
"inclusive_max": true
}
]
}
]
},
{
"conjuncts": [
{
"conjuncts": [
{
"field": "date",
"start": "2004-09-02T22:15:00Z",
"end": "2008-06-22T15:06:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number2",
"min": 876843,
"max": 3363351,
"inclusive_min": true,
"inclusive_max": true
}
]
},
{
"conjuncts": [
{
"field": "date",
"start": "2000-12-03T21:35:00Z",
"end": "2008-02-07T05:00:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number",
"min": 2021479,
"max": 4763404,
"inclusive_min": true,
"inclusive_max": true
}
]
}
]
}
]
},
{
"conjuncts": [
{
"conjuncts": [
{
"field": "date3",
"start": "2000-03-13T07:13:00Z",
"end": "2005-09-19T09:33:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number",
"min": 883125,
"max": 4817433,
"inclusive_min": true,
"inclusive_max": true
}
]
},
{
"conjuncts": [
{
"field": "date",
"start": "2002-08-10T22:42:00Z",
"end": "2008-02-10T23:19:00Z",
"inclusive_start": true,
"inclusive_end": true
},
{
"field": "number",
"min": 896115,
"max": 3897074,
"inclusive_min": true,
"inclusive_max": true
}
]
}
]
}
]
}`,
expFields: []string{"date", "number", "date2", "number2", "date3"},
},
{
query: `{
"query" : "hardworking people"
}`,
expFields: []string{"_all"},
},
{
query: `{
"query" : "text:hardworking people"
}`,
expFields: []string{"text", "_all"},
},
{
query: `{
"query" : "text:\"hardworking people\""
}`,
expFields: []string{"text"},
},
}
m := mapping.NewIndexMapping()
for i, test := range testQueries {
q, err := ParseQuery([]byte(test.query))
if err != nil {
t.Fatal(err)
}
fields, err := ExtractFields(q, m, nil)
if err != nil {
t.Fatal(err)
}
var fieldsSlice []string
for k := range fields {
fieldsSlice = append(fieldsSlice, k)
}
sort.Strings(test.expFields)
sort.Strings(fieldsSlice)
if !reflect.DeepEqual(fieldsSlice, test.expFields) {
t.Errorf("Test %d: expected %v, got %v", i, test.expFields, fieldsSlice)
}
}
}