From c9619f035973fe321c82ef78819856cf717600e2 Mon Sep 17 00:00:00 2001 From: Patrick Mezard Date: Fri, 23 Oct 2015 14:24:22 +0200 Subject: [PATCH] query: add DumpQuery to expand string query and format them as JSON This is convenient to see either complicated queries build programmatically, or to make sure the query parser does what it is expected to do. Note only queries made of bleve basic queries are supported. If we wanted to support external queries, for instance string queries with an alternative parser, I suggest to introduce some kind of: type ExpandableQuery interface { Query Expand(*IndexMapping) (Query, error) } and type assert to that instead of *queryStringQuery. --- query.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ query_test.go | 52 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/query.go b/query.go index b2c97989..a4fa21fd 100644 --- a/query.go +++ b/query.go @@ -11,6 +11,7 @@ package bleve import ( "encoding/json" + "fmt" "github.com/blevesearch/bleve/index" "github.com/blevesearch/bleve/search" @@ -209,3 +210,90 @@ func ParseQuery(input []byte) (Query, error) { } return nil, ErrorUnknownQueryType } + +// 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) + children, err := expandSlice(q.TermQueries) + if err != nil { + return nil, err + } + q.TermQueries = children + 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 +} diff --git a/query_test.go b/query_test.go index ef51c603..8933ccf8 100644 --- a/query_test.go +++ b/query_test.go @@ -11,6 +11,7 @@ package bleve import ( "reflect" + "strings" "testing" ) @@ -232,3 +233,54 @@ func TestQueryValidate(t *testing.T) { } } } + +func TestDumpQuery(t *testing.T) { + mapping := &IndexMapping{} + 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", + "boost": 1, + "prefix_length": 0, + "fuzziness": 0 + } + ], + "boost": 1 + }, + "should": { + "disjuncts": [ + { + "match": "beer", + "boost": 1, + "prefix_length": 0, + "fuzziness": 0 + } + ], + "boost": 1, + "min": 1 + }, + "must_not": { + "disjuncts": [ + { + "match": "light", + "boost": 1, + "prefix_length": 0, + "fuzziness": 0 + } + ], + "boost": 1, + "min": 0 + }, + "boost": 1 +}`) + if wanted != s { + t.Fatalf("query:\n%s\ndiffers from expected:\n%s", s, wanted) + } +}