2014-04-17 22:55:53 +02:00
|
|
|
// 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.
|
2014-08-29 20:18:36 +02:00
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
package bleve
|
2014-04-17 22:55:53 +02:00
|
|
|
|
|
|
|
import (
|
2014-07-03 20:54:50 +02:00
|
|
|
"encoding/json"
|
2015-10-23 14:24:22 +02:00
|
|
|
"fmt"
|
2014-07-03 20:54:50 +02:00
|
|
|
|
2014-09-12 23:21:35 +02:00
|
|
|
"github.com/blevesearch/bleve/index"
|
2014-08-28 21:38:57 +02:00
|
|
|
"github.com/blevesearch/bleve/search"
|
2014-04-17 22:55:53 +02:00
|
|
|
)
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// A Query represents a description of the type
|
|
|
|
// and parameters for a query into the index.
|
2014-04-17 22:55:53 +02:00
|
|
|
type Query interface {
|
|
|
|
Boost() float64
|
2014-08-30 00:14:12 +02:00
|
|
|
SetBoost(b float64) Query
|
|
|
|
Field() string
|
|
|
|
SetField(f string) Query
|
2014-09-12 23:21:35 +02:00
|
|
|
Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error)
|
2014-04-17 22:55:53 +02:00
|
|
|
Validate() error
|
|
|
|
}
|
2014-07-03 20:54:50 +02:00
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// ParseQuery deserializes a JSON representation of
|
|
|
|
// a Query object.
|
2014-07-30 18:30:38 +02:00
|
|
|
func ParseQuery(input []byte) (Query, error) {
|
2014-07-03 20:54:50 +02:00
|
|
|
var tmp map[string]interface{}
|
|
|
|
err := json.Unmarshal(input, &tmp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-24 19:39:48 +02:00
|
|
|
_, isMatchQuery := tmp["match"]
|
2014-10-23 19:02:29 +02:00
|
|
|
_, hasFuzziness := tmp["fuzziness"]
|
2014-10-24 19:39:48 +02:00
|
|
|
if hasFuzziness && !isMatchQuery {
|
2014-10-23 19:02:29 +02:00
|
|
|
var rv fuzzyQuery
|
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
|
|
|
return &rv, nil
|
|
|
|
}
|
2014-07-03 20:54:50 +02:00
|
|
|
_, isTermQuery := tmp["term"]
|
|
|
|
if isTermQuery {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv termQuery
|
2014-07-03 20:54:50 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
if isMatchQuery {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv matchQuery
|
2014-07-11 20:49:59 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
_, isMatchPhraseQuery := tmp["match_phrase"]
|
|
|
|
if isMatchPhraseQuery {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv matchPhraseQuery
|
2014-07-11 20:49:59 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
2014-07-03 20:54:50 +02:00
|
|
|
}
|
|
|
|
_, hasMust := tmp["must"]
|
|
|
|
_, hasShould := tmp["should"]
|
|
|
|
_, hasMustNot := tmp["must_not"]
|
|
|
|
if hasMust || hasShould || hasMustNot {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv booleanQuery
|
2014-07-03 20:54:50 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
2014-07-03 20:54:50 +02:00
|
|
|
}
|
|
|
|
_, hasTerms := tmp["terms"]
|
|
|
|
if hasTerms {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv phraseQuery
|
2014-07-11 20:49:59 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
2014-08-30 00:14:12 +02:00
|
|
|
_, hasConjuncts := tmp["conjuncts"]
|
|
|
|
if hasConjuncts {
|
|
|
|
var rv conjunctionQuery
|
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
_, hasDisjuncts := tmp["disjuncts"]
|
|
|
|
if hasDisjuncts {
|
|
|
|
var rv disjunctionQuery
|
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
|
2014-07-11 20:49:59 +02:00
|
|
|
_, hasSyntaxQuery := tmp["query"]
|
|
|
|
if hasSyntaxQuery {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv queryStringQuery
|
2014-07-03 20:54:50 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-07-11 20:49:59 +02:00
|
|
|
return &rv, nil
|
2014-07-03 20:54:50 +02:00
|
|
|
}
|
2014-08-03 01:05:58 +02:00
|
|
|
_, hasMin := tmp["min"]
|
|
|
|
_, hasMax := tmp["max"]
|
|
|
|
if hasMin || hasMax {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv numericRangeQuery
|
2014-08-03 01:05:58 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-08-03 01:05:58 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
2014-08-03 23:19:04 +02:00
|
|
|
_, hasStart := tmp["start"]
|
|
|
|
_, hasEnd := tmp["end"]
|
|
|
|
if hasStart || hasEnd {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv dateRangeQuery
|
2014-08-03 23:19:04 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-08-03 23:19:04 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
2014-08-07 19:45:39 +02:00
|
|
|
_, hasPrefix := tmp["prefix"]
|
|
|
|
if hasPrefix {
|
2014-08-30 00:14:12 +02:00
|
|
|
var rv prefixQuery
|
2014-08-07 19:45:39 +02:00
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 21:49:35 +02:00
|
|
|
if rv.Boost() == 0 {
|
|
|
|
rv.SetBoost(1)
|
|
|
|
}
|
2014-08-07 19:45:39 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
2015-03-11 21:57:22 +01:00
|
|
|
_, hasRegexp := tmp["regexp"]
|
|
|
|
if hasRegexp {
|
|
|
|
var rv regexpQuery
|
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
_, hasWildcard := tmp["wildcard"]
|
|
|
|
if hasWildcard {
|
|
|
|
var rv wildcardQuery
|
|
|
|
err := json.Unmarshal(input, &rv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &rv, nil
|
|
|
|
}
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorUnknownQueryType
|
2014-07-03 20:54:50 +02:00
|
|
|
}
|
2015-10-23 14:24:22 +02:00
|
|
|
|
|
|
|
// expandQuery traverses the input query tree and returns a new tree where
|
|
|
|
// query string queries have been expanded into base queries. Returned tree may
|
|
|
|
// reference queries from the input tree or new queries.
|
|
|
|
func expandQuery(m *IndexMapping, query Query) (Query, error) {
|
|
|
|
var expand func(query Query) (Query, error)
|
|
|
|
var expandSlice func(queries []Query) ([]Query, error)
|
|
|
|
|
|
|
|
expandSlice = func(queries []Query) ([]Query, error) {
|
|
|
|
expanded := []Query{}
|
|
|
|
for _, q := range queries {
|
|
|
|
exp, err := expand(q)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
expanded = append(expanded, exp)
|
|
|
|
}
|
|
|
|
return expanded, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
expand = func(query Query) (Query, error) {
|
|
|
|
switch query.(type) {
|
|
|
|
case *queryStringQuery:
|
|
|
|
q := query.(*queryStringQuery)
|
|
|
|
parsed, err := parseQuerySyntax(q.Query, m)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not parse '%s': %s", q.Query, err)
|
|
|
|
}
|
|
|
|
return expand(parsed)
|
|
|
|
case *conjunctionQuery:
|
|
|
|
q := *query.(*conjunctionQuery)
|
|
|
|
children, err := expandSlice(q.Conjuncts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
q.Conjuncts = children
|
|
|
|
return &q, nil
|
|
|
|
case *disjunctionQuery:
|
|
|
|
q := *query.(*disjunctionQuery)
|
|
|
|
children, err := expandSlice(q.Disjuncts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
q.Disjuncts = children
|
|
|
|
return &q, nil
|
|
|
|
case *booleanQuery:
|
|
|
|
q := *query.(*booleanQuery)
|
|
|
|
var err error
|
|
|
|
q.Must, err = expand(q.Must)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
q.Should, err = expand(q.Should)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
q.MustNot, err = expand(q.MustNot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &q, nil
|
|
|
|
case *phraseQuery:
|
|
|
|
q := *query.(*phraseQuery)
|
2015-10-31 12:01:19 +01:00
|
|
|
children, err := expandSlice(q.termQueries)
|
2015-10-23 14:24:22 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-31 12:01:19 +01:00
|
|
|
q.termQueries = children
|
2015-10-23 14:24:22 +02:00
|
|
|
return &q, nil
|
|
|
|
default:
|
|
|
|
return query, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return expand(query)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DumpQuery returns a string representation of the query tree, where query
|
|
|
|
// string queries have been expanded into base queries. The output format is
|
|
|
|
// meant for debugging purpose and may change in the future.
|
|
|
|
func DumpQuery(m *IndexMapping, query Query) (string, error) {
|
|
|
|
q, err := expandQuery(m, query)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
data, err := json.MarshalIndent(q, "", " ")
|
|
|
|
return string(data), err
|
|
|
|
}
|