|
- // 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 (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "log"
-
- "github.com/blevesearch/bleve/index"
- "github.com/blevesearch/bleve/mapping"
- "github.com/blevesearch/bleve/search"
- )
-
- var logger = log.New(ioutil.Discard, "bleve mapping ", log.LstdFlags)
-
- // SetLog sets the logger used for logging
- // by default log messages are sent to ioutil.Discard
- func SetLog(l *log.Logger) {
- logger = l
- }
-
- // A Query represents a description of the type
- // and parameters for a query into the index.
- type Query interface {
- Searcher(i index.IndexReader, m mapping.IndexMapping,
- options search.SearcherOptions) (search.Searcher, error)
- }
-
- // A BoostableQuery represents a Query which can be boosted
- // relative to other queries.
- type BoostableQuery interface {
- Query
- SetBoost(b float64)
- Boost() float64
- }
-
- // A FieldableQuery represents a Query which can be restricted
- // to a single field.
- type FieldableQuery interface {
- Query
- SetField(f string)
- Field() string
- }
-
- // A ValidatableQuery represents a Query which can be validated
- // prior to execution.
- type ValidatableQuery interface {
- Query
- Validate() error
- }
-
- // ParseQuery deserializes a JSON representation of
- // a Query object.
- func ParseQuery(input []byte) (Query, error) {
- var tmp map[string]interface{}
- err := json.Unmarshal(input, &tmp)
- if err != nil {
- return nil, err
- }
- _, isMatchQuery := tmp["match"]
- _, hasFuzziness := tmp["fuzziness"]
- if hasFuzziness && !isMatchQuery {
- var rv FuzzyQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, isTermQuery := tmp["term"]
- if isTermQuery {
- var rv TermQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- if isMatchQuery {
- var rv MatchQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, isMatchPhraseQuery := tmp["match_phrase"]
- if isMatchPhraseQuery {
- var rv MatchPhraseQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasMust := tmp["must"]
- _, hasShould := tmp["should"]
- _, hasMustNot := tmp["must_not"]
- if hasMust || hasShould || hasMustNot {
- var rv BooleanQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasTerms := tmp["terms"]
- if hasTerms {
- var rv PhraseQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- // now try multi-phrase
- var rv2 MultiPhraseQuery
- err = json.Unmarshal(input, &rv2)
- if err != nil {
- return nil, err
- }
- return &rv2, nil
- }
- return &rv, nil
- }
- _, hasConjuncts := tmp["conjuncts"]
- if hasConjuncts {
- var rv ConjunctionQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasDisjuncts := tmp["disjuncts"]
- if hasDisjuncts {
- var rv DisjunctionQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
-
- _, hasSyntaxQuery := tmp["query"]
- if hasSyntaxQuery {
- var rv QueryStringQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasMin := tmp["min"].(float64)
- _, hasMax := tmp["max"].(float64)
- if hasMin || hasMax {
- var rv NumericRangeQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasMinStr := tmp["min"].(string)
- _, hasMaxStr := tmp["max"].(string)
- if hasMinStr || hasMaxStr {
- var rv TermRangeQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasStart := tmp["start"]
- _, hasEnd := tmp["end"]
- if hasStart || hasEnd {
- var rv DateRangeQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasPrefix := tmp["prefix"]
- if hasPrefix {
- var rv PrefixQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, 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
- }
- _, hasMatchAll := tmp["match_all"]
- if hasMatchAll {
- var rv MatchAllQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasMatchNone := tmp["match_none"]
- if hasMatchNone {
- var rv MatchNoneQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasDocIds := tmp["ids"]
- if hasDocIds {
- var rv DocIDQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasBool := tmp["bool"]
- if hasBool {
- var rv BoolFieldQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasTopLeft := tmp["top_left"]
- _, hasBottomRight := tmp["bottom_right"]
- if hasTopLeft && hasBottomRight {
- var rv GeoBoundingBoxQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- _, hasDistance := tmp["distance"]
- if hasDistance {
- var rv GeoDistanceQuery
- err := json.Unmarshal(input, &rv)
- if err != nil {
- return nil, err
- }
- return &rv, nil
- }
- return nil, fmt.Errorf("unknown query type")
- }
-
- // 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 mapping.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 q := query.(type) {
- case *QueryStringQuery:
- parsed, err := parseQuerySyntax(q.Query)
- if err != nil {
- return nil, fmt.Errorf("could not parse '%s': %s", q.Query, err)
- }
- return expand(parsed)
- case *ConjunctionQuery:
- children, err := expandSlice(q.Conjuncts)
- if err != nil {
- return nil, err
- }
- q.Conjuncts = children
- return q, nil
- case *DisjunctionQuery:
- children, err := expandSlice(q.Disjuncts)
- if err != nil {
- return nil, err
- }
- q.Disjuncts = children
- return q, nil
- case *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
- 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 mapping.IndexMapping, query Query) (string, error) {
- q, err := expandQuery(m, query)
- if err != nil {
- return "", err
- }
- data, err := json.MarshalIndent(q, "", " ")
- return string(data), err
- }
|