// parseExpr1 handles OR expressions // // expr1 = expr2 {"OR" expr2} func (p *Parser) parseExpr1(ctx context) (tokType, bleve.Query, error) { queries := []bleve.Query{} prefixes := []tokType{} for { prefix, q, err := p.parseExpr2(ctx) if err != nil { return tEOF, nil, err } prefixes = append(prefixes, prefix) queries = append(queries, q) tok := p.next() if tok.typ != tOR { p.backup() break } } // let single, non-OR expressions bubble upward, prefix intact if len(queries) == 1 { return prefixes[0], queries[0], nil } // KLUDGINESS - prefixes on terms in OR expressions // we'll ignore "+" and treat "-" as NOT // eg: // `+alice OR -bob OR chuck` => `alice OR (NOT bob) OR chuck` for i, _ := range queries { if prefixes[i] == tMINUS { queries[i] = bleve.NewBooleanQuery( []bleve.Query{}, []bleve.Query{}, []bleve.Query{queries[i]}, // mustNot ) } } return tEOF, bleve.NewDisjunctionQuery(queries), nil }
func bleveQueryFromExpr(expr query.Expr) (bleve.Query, error) { if e, ok := expr.(*query.FieldExpr); ok { return bleve.NewPhraseQuery([]string{e.Term}, e.Field), nil } else if e, ok := expr.(*query.BinaryExpr); ok { lhs, err := bleveQueryFromExpr(e.LHS) if err != nil { return nil, err } rhs, err := bleveQueryFromExpr(e.RHS) if err != nil { return nil, err } if e.Op == query.AND { return bleve.NewConjunctionQuery([]bleve.Query{lhs, rhs}), nil } else if e.Op == query.OR { return bleve.NewDisjunctionQuery([]bleve.Query{lhs, rhs}), nil } } return nil, nil }
func query(term, highlight string, index bleve.Index, u content.User, feedIds []data.FeedId, paging ...int) (ua []content.UserArticle, err error) { var query bleve.Query query = bleve.NewQueryStringQuery(term) if len(feedIds) > 0 { queries := make([]bleve.Query, len(feedIds)) conjunct := make([]bleve.Query, 2) for i, id := range feedIds { q := bleve.NewTermQuery(strconv.FormatInt(int64(id), 10)) q.SetField("FeedId") queries[i] = q } disjunct := bleve.NewDisjunctionQuery(queries) conjunct[0] = query conjunct[1] = disjunct query = bleve.NewConjunctionQuery(conjunct) } searchRequest := bleve.NewSearchRequest(query) if highlight != "" { searchRequest.Highlight = bleve.NewHighlightWithStyle(highlight) } limit, offset := pagingLimit(paging) searchRequest.Size = limit searchRequest.From = offset searchResult, err := index.Search(searchRequest) if err != nil { return } if len(searchResult.Hits) == 0 { return } articleIds := []data.ArticleId{} hitMap := map[data.ArticleId]*search.DocumentMatch{} for _, hit := range searchResult.Hits { if articleId, err := strconv.ParseInt(hit.ID, 10, 64); err == nil { id := data.ArticleId(articleId) articleIds = append(articleIds, id) hitMap[id] = hit } } ua = u.ArticlesById(articleIds) if u.HasErr() { return ua, u.Err() } for i := range ua { data := ua[i].Data() hit := hitMap[data.Id] if len(hit.Fragments) > 0 { data.Hit.Fragments = hit.Fragments ua[i].Data(data) } } return }
func TestQuerySyntaxParserValid(t *testing.T) { fivePointOh := 5.0 onePointOh := 1.0 theTruth := true theFalsehood := false jan_01_2015 := numeric_util.Int64ToFloat64(time.Date(2015, time.January, 01, 0, 0, 0, 0, time.UTC).UnixNano()) jan_02_2015 := numeric_util.Int64ToFloat64(time.Date(2015, time.January, 02, 0, 0, 0, 0, time.UTC).UnixNano()) mar_15_2015 := numeric_util.Int64ToFloat64(time.Date(2015, time.March, 15, 0, 0, 0, 0, time.UTC).UnixNano()) mar_16_2015 := numeric_util.Int64ToFloat64(time.Date(2015, time.March, 16, 0, 0, 0, 0, time.UTC).UnixNano()) tests := []struct { input string result bleve.Query mapping *bleve.IndexMapping }{ { input: "test", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test"), }, { input: `"test phrase 1"`, mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test phrase 1"), }, { input: "field:test", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test").SetField("field"), }, // - is allowed inside a term, just not the start { input: "field:t-est", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("t-est").SetField("field"), }, // + is allowed inside a term, just not the start { input: "field:t+est", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("t+est").SetField("field"), }, // > is allowed inside a term, just not the start { input: "field:t>est", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("t>est").SetField("field"), }, // < is allowed inside a term, just not the start { input: "field:t<est", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("t<est").SetField("field"), }, // = is allowed inside a term, just not the start { input: "field:t=est", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("t=est").SetField("field"), }, { input: "+field1:test1", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test1").SetField("field1"), }, { input: "-field2:test2", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, nil, []bleve.Query{ bleve.NewMatchPhraseQuery("test2").SetField("field2"), }), }, { input: `field3:"test phrase 2"`, mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test phrase 2").SetField("field3"), }, { input: `+field4:"test phrase 1"`, mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test phrase 1").SetField("field4"), }, { input: `-field5:"test phrase 2"`, mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, nil, []bleve.Query{ bleve.NewMatchPhraseQuery("test phrase 2").SetField("field5"), }), }, { input: `+field6:test3 -field7:test4 field8:test5`, mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( []bleve.Query{ bleve.NewMatchPhraseQuery("test3").SetField("field6"), }, []bleve.Query{ bleve.NewMatchPhraseQuery("test5").SetField("field8"), }, []bleve.Query{ bleve.NewMatchPhraseQuery("test4").SetField("field7"), }), }, { input: "test^3", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("test").SetBoost(3.0), }, { input: "test^3 other^6", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchPhraseQuery("test").SetBoost(3.0), bleve.NewMatchPhraseQuery("other").SetBoost(6.0), }, nil, ), }, { input: "33", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("33"), }, { input: "field:33", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("33").SetField("field"), }, { input: "cat-dog", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("cat-dog"), }, /* // TODO: MatchPhraseQuery doesn't handle fuzziness... { input: "watex~", mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("watex").SetFuzziness(1), }, { input: "watex~2", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchQuery("watex").SetFuzziness(2), }, nil), }, { input: "watex~ 2", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchQuery("watex").SetFuzziness(1), bleve.NewMatchQuery("2"), }, nil), }, { input: "field:watex~", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchQuery("watex").SetFuzziness(1).SetField("field"), }, nil), }, { input: "field:watex~2", mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchQuery("watex").SetFuzziness(2).SetField("field"), }, nil), }, */ { input: `field:555c3bb06f7a127cda000005`, mapping: NewIndexMapping(), result: bleve.NewMatchPhraseQuery("555c3bb06f7a127cda000005").SetField("field"), }, { input: `field:>5`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theFalsehood, nil).SetField("field"), }, { input: `field:>=5`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theTruth, nil).SetField("field"), }, { input: `field:<5`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theFalsehood).SetField("field"), }, { input: `field:<=5`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth).SetField("field"), }, { input: `grapefruit AND lemon`, mapping: NewIndexMapping(), result: bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit"), bleve.NewMatchPhraseQuery("lemon"), }), }, { input: `grapefruit OR lemon`, mapping: NewIndexMapping(), result: bleve.NewDisjunctionQuery([]bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit"), bleve.NewMatchPhraseQuery("lemon"), }), }, { // default operator is OR input: `grapefruit lemon`, mapping: NewIndexMapping(), result: bleve.NewBooleanQuery( nil, []bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit"), bleve.NewMatchPhraseQuery("lemon"), }, nil, ), }, { input: `grapefruit AND NOT lemon`, mapping: NewIndexMapping(), result: bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit"), bleve.NewBooleanQuery(nil, nil, []bleve.Query{bleve.NewMatchPhraseQuery("lemon")}), }), }, { input: `field:(grapefruit AND lemon)`, mapping: NewIndexMapping(), result: bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit").SetField("field"), bleve.NewMatchPhraseQuery("lemon").SetField("field"), }), }, { input: `-field:(grapefruit AND lemon)`, mapping: NewIndexMapping(), result: bleve.NewBooleanQuery(nil, nil, []bleve.Query{ bleve.NewConjunctionQuery([]bleve.Query{ bleve.NewMatchPhraseQuery("grapefruit").SetField("field"), bleve.NewMatchPhraseQuery("lemon").SetField("field"), }), }), }, { input: `shoesize:[1 TO 5]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theTruth, &theTruth).SetField("shoesize"), }, { input: `shoesize:{1 TO 5}`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theFalsehood, &theFalsehood).SetField("shoesize"), }, { input: `shoesize:[1 TO 5}`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theTruth, &theFalsehood).SetField("shoesize"), }, { input: `shoesize:{1 TO 5]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theFalsehood, &theTruth).SetField("shoesize"), }, { input: `shoesize:[ TO 5]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth).SetField("shoesize"), }, { input: `shoesize:[1 TO ]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&onePointOh, nil, &theTruth, nil).SetField("shoesize"), }, // date ranges (note that endpoints and inclusivity might be modified by the parser) { input: `when:[2015-01-01 TO 2015-03-15]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&jan_01_2015, &mar_16_2015, &theTruth, &theFalsehood).SetField("when"), }, { input: `when:{2015-01-01 TO 2015-03-15]`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&jan_02_2015, &mar_16_2015, &theTruth, &theFalsehood).SetField("when"), }, { input: `when:[2015-01-01 TO 2015-03-15}`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&jan_01_2015, &mar_15_2015, &theTruth, &theFalsehood).SetField("when"), }, { input: `when:>2015-03-15`, mapping: NewIndexMapping(), result: bleve.NewNumericRangeInclusiveQuery(&mar_16_2015, nil, &theTruth, nil).SetField("when"), }, } for _, test := range tests { q, err := Parse(test.input) if err != nil { t.Error(err) } if !reflect.DeepEqual(q, test.result) { t.Errorf("Expected %#v, got %#v: for `%s`", test.result, q, test.input) // t.Errorf("Expected %#v, got %#v: for %s", test.result.(*booleanQuery).Should.(*disjunctionQuery).Disjuncts[0], q.(*booleanQuery).Should.(*disjunctionQuery).Disjuncts[0], test.input) } } }