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)
		}
	}
}
Beispiel #2
0
// NewDisjunctionQuery creates a new compound Query.
// Result documents satisfy at least one Query.
func NewDisjunctionQuery(disjuncts ...query.Query) *query.DisjunctionQuery {
	return query.NewDisjunctionQuery(disjuncts)
}
Beispiel #3
0
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)
}