You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
4.4 KiB

  1. // Copyright (c) 2014 Couchbase, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package query
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "github.com/blevesearch/bleve/index"
  19. "github.com/blevesearch/bleve/mapping"
  20. "github.com/blevesearch/bleve/search"
  21. )
  22. type MatchQuery struct {
  23. Match string `json:"match"`
  24. FieldVal string `json:"field,omitempty"`
  25. Analyzer string `json:"analyzer,omitempty"`
  26. BoostVal *Boost `json:"boost,omitempty"`
  27. Prefix int `json:"prefix_length"`
  28. Fuzziness int `json:"fuzziness"`
  29. Operator MatchQueryOperator `json:"operator,omitempty"`
  30. }
  31. type MatchQueryOperator int
  32. const (
  33. // Document must satisfy AT LEAST ONE of term searches.
  34. MatchQueryOperatorOr = 0
  35. // Document must satisfy ALL of term searches.
  36. MatchQueryOperatorAnd = 1
  37. )
  38. func (o MatchQueryOperator) MarshalJSON() ([]byte, error) {
  39. switch o {
  40. case MatchQueryOperatorOr:
  41. return json.Marshal("or")
  42. case MatchQueryOperatorAnd:
  43. return json.Marshal("and")
  44. default:
  45. return nil, fmt.Errorf("cannot marshal match operator %d to JSON", o)
  46. }
  47. }
  48. func (o *MatchQueryOperator) UnmarshalJSON(data []byte) error {
  49. var operatorString string
  50. err := json.Unmarshal(data, &operatorString)
  51. if err != nil {
  52. return err
  53. }
  54. switch operatorString {
  55. case "or":
  56. *o = MatchQueryOperatorOr
  57. return nil
  58. case "and":
  59. *o = MatchQueryOperatorAnd
  60. return nil
  61. default:
  62. return fmt.Errorf("cannot unmarshal match operator '%v' from JSON", o)
  63. }
  64. }
  65. // NewMatchQuery creates a Query for matching text.
  66. // An Analyzer is chosen based on the field.
  67. // Input text is analyzed using this analyzer.
  68. // Token terms resulting from this analysis are
  69. // used to perform term searches. Result documents
  70. // must satisfy at least one of these term searches.
  71. func NewMatchQuery(match string) *MatchQuery {
  72. return &MatchQuery{
  73. Match: match,
  74. Operator: MatchQueryOperatorOr,
  75. }
  76. }
  77. func (q *MatchQuery) SetBoost(b float64) {
  78. boost := Boost(b)
  79. q.BoostVal = &boost
  80. }
  81. func (q *MatchQuery) Boost() float64{
  82. return q.BoostVal.Value()
  83. }
  84. func (q *MatchQuery) SetField(f string) {
  85. q.FieldVal = f
  86. }
  87. func (q *MatchQuery) Field() string{
  88. return q.FieldVal
  89. }
  90. func (q *MatchQuery) SetFuzziness(f int) {
  91. q.Fuzziness = f
  92. }
  93. func (q *MatchQuery) SetPrefix(p int) {
  94. q.Prefix = p
  95. }
  96. func (q *MatchQuery) SetOperator(operator MatchQueryOperator) {
  97. q.Operator = operator
  98. }
  99. func (q *MatchQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
  100. field := q.FieldVal
  101. if q.FieldVal == "" {
  102. field = m.DefaultSearchField()
  103. }
  104. analyzerName := ""
  105. if q.Analyzer != "" {
  106. analyzerName = q.Analyzer
  107. } else {
  108. analyzerName = m.AnalyzerNameForPath(field)
  109. }
  110. analyzer := m.AnalyzerNamed(analyzerName)
  111. if analyzer == nil {
  112. return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
  113. }
  114. tokens := analyzer.Analyze([]byte(q.Match))
  115. if len(tokens) > 0 {
  116. tqs := make([]Query, len(tokens))
  117. if q.Fuzziness != 0 {
  118. for i, token := range tokens {
  119. query := NewFuzzyQuery(string(token.Term))
  120. query.SetFuzziness(q.Fuzziness)
  121. query.SetPrefix(q.Prefix)
  122. query.SetField(field)
  123. query.SetBoost(q.BoostVal.Value())
  124. tqs[i] = query
  125. }
  126. } else {
  127. for i, token := range tokens {
  128. tq := NewTermQuery(string(token.Term))
  129. tq.SetField(field)
  130. tq.SetBoost(q.BoostVal.Value())
  131. tqs[i] = tq
  132. }
  133. }
  134. switch q.Operator {
  135. case MatchQueryOperatorOr:
  136. shouldQuery := NewDisjunctionQuery(tqs)
  137. shouldQuery.SetMin(1)
  138. shouldQuery.SetBoost(q.BoostVal.Value())
  139. return shouldQuery.Searcher(i, m, explain)
  140. case MatchQueryOperatorAnd:
  141. mustQuery := NewConjunctionQuery(tqs)
  142. mustQuery.SetBoost(q.BoostVal.Value())
  143. return mustQuery.Searcher(i, m, explain)
  144. default:
  145. return nil, fmt.Errorf("unhandled operator %d", q.Operator)
  146. }
  147. }
  148. noneQuery := NewMatchNoneQuery()
  149. return noneQuery.Searcher(i, m, explain)
  150. }