suggester_completion.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. // Copyright 2012-present Oliver Eilhard. All rights reserved.
  2. // Use of this source code is governed by a MIT-license.
  3. // See http://olivere.mit-license.org/license.txt for details.
  4. package elastic
  5. import "errors"
  6. // CompletionSuggester is a fast suggester for e.g. type-ahead completion.
  7. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-suggesters-completion.html
  8. // for more details.
  9. type CompletionSuggester struct {
  10. Suggester
  11. name string
  12. text string
  13. prefix string
  14. regex string
  15. field string
  16. analyzer string
  17. size *int
  18. shardSize *int
  19. contextQueries []SuggesterContextQuery
  20. payload interface{}
  21. fuzzyOptions *FuzzyCompletionSuggesterOptions
  22. regexOptions *RegexCompletionSuggesterOptions
  23. }
  24. // Creates a new completion suggester.
  25. func NewCompletionSuggester(name string) *CompletionSuggester {
  26. return &CompletionSuggester{
  27. name: name,
  28. }
  29. }
  30. func (q *CompletionSuggester) Name() string {
  31. return q.name
  32. }
  33. func (q *CompletionSuggester) Text(text string) *CompletionSuggester {
  34. q.text = text
  35. return q
  36. }
  37. func (q *CompletionSuggester) Prefix(prefix string) *CompletionSuggester {
  38. q.prefix = prefix
  39. return q
  40. }
  41. func (q *CompletionSuggester) PrefixWithEditDistance(prefix string, editDistance interface{}) *CompletionSuggester {
  42. q.prefix = prefix
  43. q.fuzzyOptions = NewFuzzyCompletionSuggesterOptions().EditDistance(editDistance)
  44. return q
  45. }
  46. func (q *CompletionSuggester) PrefixWithOptions(prefix string, options *FuzzyCompletionSuggesterOptions) *CompletionSuggester {
  47. q.prefix = prefix
  48. q.fuzzyOptions = options
  49. return q
  50. }
  51. func (q *CompletionSuggester) FuzzyOptions(options *FuzzyCompletionSuggesterOptions) *CompletionSuggester {
  52. q.fuzzyOptions = options
  53. return q
  54. }
  55. func (q *CompletionSuggester) Regex(regex string) *CompletionSuggester {
  56. q.regex = regex
  57. return q
  58. }
  59. func (q *CompletionSuggester) RegexWithOptions(regex string, options *RegexCompletionSuggesterOptions) *CompletionSuggester {
  60. q.regex = regex
  61. q.regexOptions = options
  62. return q
  63. }
  64. func (q *CompletionSuggester) RegexOptions(options *RegexCompletionSuggesterOptions) *CompletionSuggester {
  65. q.regexOptions = options
  66. return q
  67. }
  68. func (q *CompletionSuggester) Field(field string) *CompletionSuggester {
  69. q.field = field
  70. return q
  71. }
  72. func (q *CompletionSuggester) Analyzer(analyzer string) *CompletionSuggester {
  73. q.analyzer = analyzer
  74. return q
  75. }
  76. func (q *CompletionSuggester) Size(size int) *CompletionSuggester {
  77. q.size = &size
  78. return q
  79. }
  80. func (q *CompletionSuggester) ShardSize(shardSize int) *CompletionSuggester {
  81. q.shardSize = &shardSize
  82. return q
  83. }
  84. func (q *CompletionSuggester) ContextQuery(query SuggesterContextQuery) *CompletionSuggester {
  85. q.contextQueries = append(q.contextQueries, query)
  86. return q
  87. }
  88. func (q *CompletionSuggester) ContextQueries(queries ...SuggesterContextQuery) *CompletionSuggester {
  89. q.contextQueries = append(q.contextQueries, queries...)
  90. return q
  91. }
  92. // completionSuggesterRequest is necessary because the order in which
  93. // the JSON elements are routed to Elasticsearch is relevant.
  94. // We got into trouble when using plain maps because the text element
  95. // needs to go before the completion element.
  96. type completionSuggesterRequest struct {
  97. Text string `json:"text,omitempty"`
  98. Prefix string `json:"prefix,omitempty"`
  99. Regex string `json:"regex,omitempty"`
  100. Completion interface{} `json:"completion,omitempty"`
  101. }
  102. // Source creates the JSON data for the completion suggester.
  103. func (q *CompletionSuggester) Source(includeName bool) (interface{}, error) {
  104. cs := &completionSuggesterRequest{}
  105. if q.text != "" {
  106. cs.Text = q.text
  107. }
  108. if q.prefix != "" {
  109. cs.Prefix = q.prefix
  110. }
  111. if q.regex != "" {
  112. cs.Regex = q.regex
  113. }
  114. suggester := make(map[string]interface{})
  115. cs.Completion = suggester
  116. if q.analyzer != "" {
  117. suggester["analyzer"] = q.analyzer
  118. }
  119. if q.field != "" {
  120. suggester["field"] = q.field
  121. }
  122. if q.size != nil {
  123. suggester["size"] = *q.size
  124. }
  125. if q.shardSize != nil {
  126. suggester["shard_size"] = *q.shardSize
  127. }
  128. switch len(q.contextQueries) {
  129. case 0:
  130. case 1:
  131. src, err := q.contextQueries[0].Source()
  132. if err != nil {
  133. return nil, err
  134. }
  135. suggester["context"] = src
  136. default:
  137. ctxq := make(map[string]interface{})
  138. for _, query := range q.contextQueries {
  139. src, err := query.Source()
  140. if err != nil {
  141. return nil, err
  142. }
  143. // Merge the dictionary into ctxq
  144. m, ok := src.(map[string]interface{})
  145. if !ok {
  146. return nil, errors.New("elastic: context query is not a map")
  147. }
  148. for k, v := range m {
  149. ctxq[k] = v
  150. }
  151. }
  152. suggester["contexts"] = ctxq
  153. }
  154. // Fuzzy options
  155. if q.fuzzyOptions != nil {
  156. src, err := q.fuzzyOptions.Source()
  157. if err != nil {
  158. return nil, err
  159. }
  160. suggester["fuzzy"] = src
  161. }
  162. // Regex options
  163. if q.regexOptions != nil {
  164. src, err := q.regexOptions.Source()
  165. if err != nil {
  166. return nil, err
  167. }
  168. suggester["regex"] = src
  169. }
  170. // TODO(oe) Add completion-suggester specific parameters here
  171. if !includeName {
  172. return cs, nil
  173. }
  174. source := make(map[string]interface{})
  175. source[q.name] = cs
  176. return source, nil
  177. }
  178. // -- Fuzzy options --
  179. // FuzzyCompletionSuggesterOptions represents the options for fuzzy completion suggester.
  180. type FuzzyCompletionSuggesterOptions struct {
  181. editDistance interface{}
  182. transpositions *bool
  183. minLength *int
  184. prefixLength *int
  185. unicodeAware *bool
  186. maxDeterminizedStates *int
  187. }
  188. // NewFuzzyCompletionSuggesterOptions initializes a new FuzzyCompletionSuggesterOptions instance.
  189. func NewFuzzyCompletionSuggesterOptions() *FuzzyCompletionSuggesterOptions {
  190. return &FuzzyCompletionSuggesterOptions{}
  191. }
  192. // EditDistance specifies the maximum number of edits, e.g. a number like "1" or "2"
  193. // or a string like "0..2" or ">5". See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#fuzziness
  194. // for details.
  195. func (o *FuzzyCompletionSuggesterOptions) EditDistance(editDistance interface{}) *FuzzyCompletionSuggesterOptions {
  196. o.editDistance = editDistance
  197. return o
  198. }
  199. // Transpositions, if set to true, are counted as one change instead of two (defaults to true).
  200. func (o *FuzzyCompletionSuggesterOptions) Transpositions(transpositions bool) *FuzzyCompletionSuggesterOptions {
  201. o.transpositions = &transpositions
  202. return o
  203. }
  204. // MinLength represents the minimum length of the input before fuzzy suggestions are returned (defaults to 3).
  205. func (o *FuzzyCompletionSuggesterOptions) MinLength(minLength int) *FuzzyCompletionSuggesterOptions {
  206. o.minLength = &minLength
  207. return o
  208. }
  209. // PrefixLength represents the minimum length of the input, which is not checked for
  210. // fuzzy alternatives (defaults to 1).
  211. func (o *FuzzyCompletionSuggesterOptions) PrefixLength(prefixLength int) *FuzzyCompletionSuggesterOptions {
  212. o.prefixLength = &prefixLength
  213. return o
  214. }
  215. // UnicodeAware, if true, all measurements (like fuzzy edit distance, transpositions, and lengths)
  216. // are measured in Unicode code points instead of in bytes. This is slightly slower than
  217. // raw bytes, so it is set to false by default.
  218. func (o *FuzzyCompletionSuggesterOptions) UnicodeAware(unicodeAware bool) *FuzzyCompletionSuggesterOptions {
  219. o.unicodeAware = &unicodeAware
  220. return o
  221. }
  222. // MaxDeterminizedStates is currently undocumented in Elasticsearch. It represents
  223. // the maximum automaton states allowed for fuzzy expansion.
  224. func (o *FuzzyCompletionSuggesterOptions) MaxDeterminizedStates(max int) *FuzzyCompletionSuggesterOptions {
  225. o.maxDeterminizedStates = &max
  226. return o
  227. }
  228. // Source creates the JSON data.
  229. func (o *FuzzyCompletionSuggesterOptions) Source() (interface{}, error) {
  230. out := make(map[string]interface{})
  231. if o.editDistance != nil {
  232. out["fuzziness"] = o.editDistance
  233. }
  234. if o.transpositions != nil {
  235. out["transpositions"] = *o.transpositions
  236. }
  237. if o.minLength != nil {
  238. out["min_length"] = *o.minLength
  239. }
  240. if o.prefixLength != nil {
  241. out["prefix_length"] = *o.prefixLength
  242. }
  243. if o.unicodeAware != nil {
  244. out["unicode_aware"] = *o.unicodeAware
  245. }
  246. if o.maxDeterminizedStates != nil {
  247. out["max_determinized_states"] = *o.maxDeterminizedStates
  248. }
  249. return out, nil
  250. }
  251. // -- Regex options --
  252. // RegexCompletionSuggesterOptions represents the options for regex completion suggester.
  253. type RegexCompletionSuggesterOptions struct {
  254. flags interface{} // string or int
  255. maxDeterminizedStates *int
  256. }
  257. // NewRegexCompletionSuggesterOptions initializes a new RegexCompletionSuggesterOptions instance.
  258. func NewRegexCompletionSuggesterOptions() *RegexCompletionSuggesterOptions {
  259. return &RegexCompletionSuggesterOptions{}
  260. }
  261. // Flags represents internal regex flags. See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-suggesters-completion.html#regex
  262. // for details.
  263. func (o *RegexCompletionSuggesterOptions) Flags(flags interface{}) *RegexCompletionSuggesterOptions {
  264. o.flags = flags
  265. return o
  266. }
  267. // MaxDeterminizedStates represents the maximum automaton states allowed for regex expansion.
  268. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-suggesters-completion.html#regex
  269. // for details.
  270. func (o *RegexCompletionSuggesterOptions) MaxDeterminizedStates(max int) *RegexCompletionSuggesterOptions {
  271. o.maxDeterminizedStates = &max
  272. return o
  273. }
  274. // Source creates the JSON data.
  275. func (o *RegexCompletionSuggesterOptions) Source() (interface{}, error) {
  276. out := make(map[string]interface{})
  277. if o.flags != nil {
  278. out["flags"] = o.flags
  279. }
  280. if o.maxDeterminizedStates != nil {
  281. out["max_determinized_states"] = *o.maxDeterminizedStates
  282. }
  283. return out, nil
  284. }