func TestQuerySyntaxParserValid(t *testing.T) { fivePointOh := 5.0 onePointOh := 1.0 theTruth := true theFalsehood := false jan_01_2015 := time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC) jan_02_2015 := time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC) mar_15_2015 := time.Date(2015, time.March, 15, 0, 0, 0, 0, time.UTC) mar_16_2015 := time.Date(2015, time.March, 16, 0, 0, 0, 0, time.UTC) /*theDate, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") if err != nil { t.Fatal(err) } */ tests := []struct { input string result Query mapping mapping.IndexMapping }{ { input: "test", mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("test"), }, { input: `"test phrase 1"`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("test phrase 1"), }, { input: "field:test", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("test") q.SetField("field") return q }(), }, // - is allowed inside a term, just not the start { input: "field:t-est", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("t-est") q.SetField("field") return q }(), }, // + is allowed inside a term, just not the start { input: "field:t+est", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("t+est") q.SetField("field") return q }(), }, // > is allowed inside a term, just not the start { input: "field:t>est", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("t>est") q.SetField("field") return q }(), }, // < is allowed inside a term, just not the start { input: "field:t<est", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("t<est") q.SetField("field") return q }(), }, // = is allowed inside a term, just not the start { input: "field:t=est", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("t=est") q.SetField("field") return q }(), }, { input: "+field1:test1", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("test1") q.SetField("field1") return q }(), }, { input: "-field2:test2", mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, nil, []Query{ func() Query { q := NewMatchPhraseQuery("test2") q.SetField("field2") return q }(), }), }, { input: `field3:"test phrase 2"`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("test phrase 2") q.SetField("field3") return q }(), }, { input: `+field4:"test phrase 1"`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("test phrase 1") q.SetField("field4") return q }(), }, { input: `-field5:"test phrase 2"`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, nil, []Query{ func() Query { q := NewMatchPhraseQuery("test phrase 2") q.SetField("field5") return q }(), }), }, { input: `+field6:test3 -field7:test4 field8:test5`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( []Query{ func() Query { q := NewMatchPhraseQuery("test3") q.SetField("field6") return q }(), }, []Query{ func() Query { q := NewMatchPhraseQuery("test5") q.SetField("field8") return q }(), }, []Query{ func() Query { q := NewMatchPhraseQuery("test4") q.SetField("field7") return q }(), }), }, { input: "test^3", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("test") q.SetBoost(3.0) return q }(), }, { input: "test^3 other^6", mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewMatchPhraseQuery("test") q.SetBoost(3.0) return q }(), func() Query { q := NewMatchPhraseQuery("other") q.SetBoost(6.0) return q }(), }, nil), }, { input: "33", mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("33"), }, { input: "field:33", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("33") q.SetField("field") return q }(), }, { input: "cat-dog", mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("cat-dog"), }, { input: "watex~", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewFuzzyQuery("watex") q.SetFuzziness(1) return q }(), }, { input: "watex~2", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewFuzzyQuery("watex") q.SetFuzziness(2) return q }(), }, { input: "watex~ 2", mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewFuzzyQuery("watex") q.SetFuzziness(1) return q }(), NewMatchPhraseQuery("2"), }, nil), }, { input: "field:watex~", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewFuzzyQuery("watex") q.SetFuzziness(1) q.SetField("field") return q }(), }, { input: "field:watex~2", mapping: mapping.NewIndexMapping(), result: func() Query { q := NewFuzzyQuery("watex") q.SetFuzziness(2) q.SetField("field") return q }(), }, { input: `field:555c3bb06f7a127cda000005`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("555c3bb06f7a127cda000005") q.SetField("field") return q }(), }, { input: `field:>5`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theFalsehood, nil) q.SetField("field") return q }(), }, { input: `field:>=5`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theTruth, nil) q.SetField("field") return q }(), }, { input: `field:<5`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theFalsehood) q.SetField("field") return q }(), }, { input: `field:<=5`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth) q.SetField("field") return q }(), }, /* XYZZY - TODO: support use of bleve dateparser { input: `field:>"2006-01-02T15:04:05Z"`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewDateRangeInclusiveQuery(theDate, time.Time{}, &theFalsehood, nil) q.SetField("field") return q }(), }, nil), }, { input: `field:>="2006-01-02T15:04:05Z"`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewDateRangeInclusiveQuery(theDate, time.Time{}, &theTruth, nil) q.SetField("field") return q }(), }, nil), }, { input: `field:<"2006-01-02T15:04:05Z"`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewDateRangeInclusiveQuery(time.Time{}, theDate, nil, &theFalsehood) q.SetField("field") return q }(), }, nil), }, { input: `field:<="2006-01-02T15:04:05Z"`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewDateRangeInclusiveQuery(time.Time{}, theDate, nil, &theTruth) q.SetField("field") return q }(), }, nil), }, */ /* XYZZY - TODO: regexp support { input: `/mar.*ty/`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ NewRegexpQuery("mar.*ty"), }, nil), }, { input: `name:/mar.*ty/`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ func() Query { q := NewRegexpQuery("mar.*ty") q.SetField("name") return q }(), }, nil), }, */ { input: `mart*`, mapping: mapping.NewIndexMapping(), result: NewWildcardQuery("mart*"), }, { input: `name:mart*`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewWildcardQuery("mart*") q.SetField("name") return q }(), }, // tests for escaping /* XYZZY // escape : as field delimeter { input: `name\:marty`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("name:marty"), }, // first colon delimiter, second escaped { input: `name:marty\:couchbase`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery("marty:couchbase") q.SetField("name") return q }(), }, // escape space, single arguemnt to match query { input: `marty\ couchbase`, mapping: mapping.NewIndexMapping(), result: NewMatchQuery("marty couchbase"), }, // escape leading plus, not a must clause { input: `\+marty`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("+marty"), }, // escape leading minus, not a must not clause { input: `\-marty`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery("-marty"), }, // escape quote inside of phrase { input: `"what does \"quote\" mean"`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery(`what does "quote" mean`), }, // escaping an unsupported character retains backslash { input: `can\ i\ escap\e`, mapping: mapping.NewIndexMapping(), result: NewMatchQuery(`can i escap\e`), }, */ // leading spaces { input: ` what`, mapping: mapping.NewIndexMapping(), result: NewMatchPhraseQuery(`what`), }, // no boost value defaults to 1 { input: `term^`, mapping: mapping.NewIndexMapping(), result: func() Query { q := NewMatchPhraseQuery(`term`) q.SetBoost(1.0) return q }(), }, /* XYZZY // weird lexer cases, something that starts like a number // but contains escape and ends up as string { input: `3.0\:`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ NewMatchQuery(`3.0:`), }, nil), }, { input: `3.0\a`, mapping: mapping.NewIndexMapping(), result: NewBooleanQuery( nil, []Query{ NewMatchQuery(`3.0\a`), }, nil), }, */ /* * Extra stuff, above what querystringquery supports * */ { input: `grapefruit AND lemon`, mapping: mapping.NewIndexMapping(), result: query.NewConjunctionQuery([]query.Query{ query.NewMatchPhraseQuery("grapefruit"), query.NewMatchPhraseQuery("lemon"), }), }, { input: `grapefruit OR lemon`, mapping: mapping.NewIndexMapping(), result: query.NewDisjunctionQuery([]query.Query{ query.NewMatchPhraseQuery("grapefruit"), query.NewMatchPhraseQuery("lemon"), }), }, { // default operator is OR input: `grapefruit lemon`, mapping: mapping.NewIndexMapping(), result: query.NewBooleanQuery( nil, []query.Query{ query.NewMatchPhraseQuery("grapefruit"), query.NewMatchPhraseQuery("lemon"), }, nil, ), }, { input: `grapefruit AND NOT lemon`, mapping: mapping.NewIndexMapping(), result: query.NewConjunctionQuery([]query.Query{ query.NewMatchPhraseQuery("grapefruit"), query.NewBooleanQuery(nil, nil, []query.Query{query.NewMatchPhraseQuery("lemon")}), }), }, { input: `field:(grapefruit AND lemon)`, mapping: mapping.NewIndexMapping(), result: func() Query { t1 := NewMatchPhraseQuery(`grapefruit`) t1.SetField("field") t2 := NewMatchPhraseQuery(`lemon`) t2.SetField("field") q := query.NewConjunctionQuery([]query.Query{t1, t2}) return q }(), }, { input: `-field:(grapefruit AND lemon)`, mapping: mapping.NewIndexMapping(), result: func() Query { t1 := NewMatchPhraseQuery(`grapefruit`) t1.SetField("field") t2 := NewMatchPhraseQuery(`lemon`) t2.SetField("field") andq := query.NewConjunctionQuery([]query.Query{t1, t2}) q := query.NewBooleanQuery(nil, nil, []query.Query{andq}) return q }(), }, { input: `shoesize:[1 TO 5]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theTruth, &theTruth) q.SetField("shoesize") return q }(), }, { input: `shoesize:{1 TO 5}`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theFalsehood, &theFalsehood) q.SetField("shoesize") return q }(), }, { input: `shoesize:[1 TO 5}`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theTruth, &theFalsehood) q.SetField("shoesize") return q }(), }, { input: `shoesize:{1 TO 5]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(&onePointOh, &fivePointOh, &theFalsehood, &theTruth) q.SetField("shoesize") return q }(), }, { input: `shoesize:[ TO 5]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth) q.SetField("shoesize") return q }(), }, { input: `shoesize:[1 TO ]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewNumericRangeInclusiveQuery(&onePointOh, nil, &theTruth, nil) q.SetField("shoesize") return q }(), }, // date ranges (note that endpoints and inclusivity might be modified by the parser) { input: `when:[2015-01-01 TO 2015-03-15]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewDateRangeInclusiveQuery(jan_01_2015, mar_16_2015, &theTruth, &theFalsehood) q.SetField("when") return q }(), }, { input: `when:{2015-01-01 TO 2015-03-15]`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewDateRangeInclusiveQuery(jan_02_2015, mar_16_2015, &theTruth, &theFalsehood) q.SetField("when") return q }(), }, { input: `when:[2015-01-01 TO 2015-03-15}`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewDateRangeInclusiveQuery(jan_01_2015, mar_15_2015, &theTruth, &theFalsehood) q.SetField("when") return q }(), }, { input: `when:>2015-03-15`, mapping: mapping.NewIndexMapping(), result: func() Query { q := query.NewDateRangeInclusiveQuery(mar_16_2015, time.Time{}, &theTruth, nil) q.SetField("when") return q }(), }, // Wildcards { input: `foo*`, mapping: mapping.NewIndexMapping(), result: query.NewWildcardQuery(`foo*`), }, { input: `f?rt`, mapping: mapping.NewIndexMapping(), result: query.NewWildcardQuery(`f?rt`), }, } for _, test := range tests { q, err := Parse(test.input) if err != nil { t.Fatalf("`%s`: %s", test.input, err) } if !reflect.DeepEqual(q, test.result) { t.Errorf("Expected %#v, got %#v: for %s", test.result, q, test.input) } } }
// NewDisjunctionQuery creates a new compound Query. // Result documents satisfy at least one Query. func NewDisjunctionQuery(disjuncts ...query.Query) *query.DisjunctionQuery { return query.NewDisjunctionQuery(disjuncts) }
func makeSearchQuery(queryString string, ids []string) (query.Query, error) { nodes, err := blevext.Parse(queryString) if err != nil { return nil, err } addIdsFilter := func(q query.Query) query.Query { if len(ids) == 0 { return q } return bleve.NewConjunctionQuery( query.NewDocIDQuery(ids), q, ) } var makeQuery func(*blevext.Node) (query.Query, error) makeQuery = func(n *blevext.Node) (query.Query, error) { if n == nil { return bleve.NewMatchAllQuery(), nil } switch n.Kind { case blevext.NodeAnd, blevext.NodeOr: left, err := makeQuery(n.Children[0]) if err != nil { return nil, err } right, err := makeQuery(n.Children[1]) if err != nil { return nil, err } if n.Kind == blevext.NodeOr { q := query.NewDisjunctionQuery([]query.Query{left, right}) q.Min = 1 return q, nil } return query.NewConjunctionQuery([]query.Query{left, right}), nil case blevext.NodeString, blevext.NodePhrase: fn := func(s string) query.FieldableQuery { return bleve.NewMatchQuery(s) } if n.Kind == blevext.NodePhrase { fn = func(s string) query.FieldableQuery { return blevext.NewAllMatchQuery(s) } } htmlQuery := fn(n.Value) htmlQuery.SetField("html") titleQuery := fn(n.Value) titleQuery.SetField("title") q := query.NewDisjunctionQuery([]query.Query{ addIdsFilter(htmlQuery), addIdsFilter(titleQuery), }) q.Min = 1 return q, nil } return nil, fmt.Errorf("unknown query node type: %d", n.Kind) } return makeQuery(nodes) }