Commit c9f2af4dc38a2ec68332b74159a2b6c0278668b8
1 parent
92d69dfb
Exists in
master
and in
21 other branches
No more Tire DSL
Showing
2 changed files
with
181 additions
and
145 deletions
Show diff stats
lib/searchkick/search.rb
@@ -35,165 +35,196 @@ module Searchkick | @@ -35,165 +35,196 @@ module Searchkick | ||
35 | 35 | ||
36 | all = term == "*" | 36 | all = term == "*" |
37 | 37 | ||
38 | - # TODO lose Tire DSL for more flexibility | ||
39 | - s = | ||
40 | - Tire::Search::Search.new do | ||
41 | - query do | ||
42 | - custom_filters_score do | ||
43 | - query do | ||
44 | - if !all | ||
45 | - boolean do | ||
46 | - must do | ||
47 | - if options[:autocomplete] | ||
48 | - match fields, term, analyzer: "searchkick_autocomplete_search" | ||
49 | - else | ||
50 | - dis_max do | ||
51 | - query do | ||
52 | - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search" | ||
53 | - end | ||
54 | - query do | ||
55 | - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search2" | ||
56 | - end | ||
57 | - query do | ||
58 | - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search" | ||
59 | - end | ||
60 | - query do | ||
61 | - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search2" | ||
62 | - end | ||
63 | - end | ||
64 | - end | ||
65 | - end | ||
66 | - if conversions_field and options[:conversions] != false | ||
67 | - should do | ||
68 | - nested path: conversions_field, score_mode: "total" do | ||
69 | - query do | ||
70 | - custom_score script: "doc['count'].value" do | ||
71 | - match "query", term | ||
72 | - end | ||
73 | - end | ||
74 | - end | ||
75 | - end | ||
76 | - end | ||
77 | - end | ||
78 | - end | ||
79 | - end | ||
80 | - if options[:boost] | ||
81 | - filter do | ||
82 | - filter :exists, field: options[:boost] | ||
83 | - script "log(doc['#{options[:boost]}'].value + 2.718281828)" | ||
84 | - end | 38 | + if all |
39 | + payload = { | ||
40 | + match_all: {} | ||
41 | + } | ||
42 | + else | ||
43 | + if options[:autocomplete] | ||
44 | + payload = { | ||
45 | + multi_match: { | ||
46 | + fields: fields, | ||
47 | + query: term, | ||
48 | + analyzer: "searchkick_autocomplete_search" | ||
49 | + } | ||
50 | + } | ||
51 | + else | ||
52 | + shared_options = { | ||
53 | + fields: fields, | ||
54 | + query: term, | ||
55 | + use_dis_max: false, | ||
56 | + operator: operator | ||
57 | + } | ||
58 | + payload = { | ||
59 | + dis_max: { | ||
60 | + queries: [ | ||
61 | + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")}, | ||
62 | + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")}, | ||
63 | + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search")}, | ||
64 | + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search2")}, | ||
65 | + ] | ||
66 | + } | ||
67 | + } | ||
68 | + end | ||
69 | + | ||
70 | + if conversions_field and options[:conversions] != false | ||
71 | + # wrap payload in a bool query | ||
72 | + payload = { | ||
73 | + bool: { | ||
74 | + must: payload, | ||
75 | + should: { | ||
76 | + nested: { | ||
77 | + path: conversions_field, | ||
78 | + score_mode: "total", | ||
79 | + query: { | ||
80 | + custom_score: { | ||
81 | + query: { | ||
82 | + match: { | ||
83 | + query: term | ||
84 | + } | ||
85 | + }, | ||
86 | + script: "doc['count'].value" | ||
87 | + } | ||
88 | + } | ||
89 | + } | ||
90 | + } | ||
91 | + } | ||
92 | + } | ||
93 | + end | ||
94 | + end | ||
95 | + | ||
96 | + custom_filters = [] | ||
97 | + | ||
98 | + if options[:boost] | ||
99 | + custom_filters << { | ||
100 | + filter: { | ||
101 | + exists: { | ||
102 | + field: options[:boost], | ||
103 | + } | ||
104 | + }, | ||
105 | + script: "log(doc['#{options[:boost]}'].value + 2.718281828)" | ||
106 | + } | ||
107 | + end | ||
108 | + | ||
109 | + if options[:user_id] and personalize_field | ||
110 | + custom_filters << { | ||
111 | + filter: { | ||
112 | + term: { | ||
113 | + personalize_field => options[:user_id] | ||
114 | + }, | ||
115 | + }, | ||
116 | + boost: 100 | ||
117 | + } | ||
118 | + end | ||
119 | + | ||
120 | + if custom_filters.any? | ||
121 | + payload = { | ||
122 | + custom_filters_score: { | ||
123 | + query: payload, | ||
124 | + filters: custom_filters, | ||
125 | + score_mode: "total" | ||
126 | + } | ||
127 | + } | ||
128 | + end | ||
129 | + | ||
130 | + payload = { | ||
131 | + query: payload, | ||
132 | + size: per_page | ||
133 | + } | ||
134 | + payload[:from] = offset if offset | ||
135 | + payload[:explain] = options[:explain] if options[:explain] | ||
136 | + | ||
137 | + # order | ||
138 | + if options[:order] | ||
139 | + order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | ||
140 | + payload[:sort] = order | ||
141 | + end | ||
142 | + | ||
143 | + # where | ||
144 | + # TODO expand or | ||
145 | + where_filters = | ||
146 | + proc do |where| | ||
147 | + filters = [] | ||
148 | + (where || {}).each do |field, value| | ||
149 | + if field == :or | ||
150 | + value.each do |or_clause| | ||
151 | + filters << {or: or_clause.map{|or_statement| {term: or_statement} }} | ||
85 | end | 152 | end |
86 | - if options[:user_id] and personalize_field | ||
87 | - filter do | ||
88 | - filter :term, personalize_field => options[:user_id] | ||
89 | - boost 100 | ||
90 | - end | 153 | + else |
154 | + # expand ranges | ||
155 | + if value.is_a?(Range) | ||
156 | + value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} | ||
91 | end | 157 | end |
92 | - score_mode "total" | ||
93 | - end | ||
94 | - end | ||
95 | - size per_page | ||
96 | - from offset if offset | ||
97 | - explain options[:explain] if options[:explain] | ||
98 | - | ||
99 | - # order | ||
100 | - if options[:order] | ||
101 | - order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | ||
102 | - sort do | ||
103 | - order.each do |k, v| | ||
104 | - by k, v | ||
105 | - end | ||
106 | - end | ||
107 | - end | ||
108 | - | ||
109 | - # where | ||
110 | - # TODO expand or | ||
111 | - where_filters = | ||
112 | - proc do |where| | ||
113 | - filters = [] | ||
114 | - (where || {}).each do |field, value| | ||
115 | - if field == :or | ||
116 | - value.each do |or_clause| | ||
117 | - filters << {or: or_clause.map{|or_statement| {term: or_statement} }} | ||
118 | - end | ||
119 | - else | ||
120 | - # expand ranges | ||
121 | - if value.is_a?(Range) | ||
122 | - value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} | ||
123 | - end | ||
124 | 158 | ||
125 | - if value.is_a?(Array) # in query | ||
126 | - filters << {terms: {field => value}} | ||
127 | - elsif value.is_a?(Hash) | ||
128 | - value.each do |op, op_value| | ||
129 | - if op == :not # not equal | ||
130 | - if op_value.is_a?(Array) | ||
131 | - filters << {not: {terms: {field => op_value}}} | ||
132 | - else | ||
133 | - filters << {not: {term: {field => op_value}}} | ||
134 | - end | ||
135 | - else | ||
136 | - range_query = | ||
137 | - case op | ||
138 | - when :gt | ||
139 | - {from: op_value, include_lower: false} | ||
140 | - when :gte | ||
141 | - {from: op_value, include_lower: true} | ||
142 | - when :lt | ||
143 | - {to: op_value, include_upper: false} | ||
144 | - when :lte | ||
145 | - {to: op_value, include_upper: true} | ||
146 | - else | ||
147 | - raise "Unknown where operator" | ||
148 | - end | ||
149 | - filters << {range: {field => range_query}} | ||
150 | - end | 159 | + if value.is_a?(Array) # in query |
160 | + filters << {terms: {field => value}} | ||
161 | + elsif value.is_a?(Hash) | ||
162 | + value.each do |op, op_value| | ||
163 | + if op == :not # not equal | ||
164 | + if op_value.is_a?(Array) | ||
165 | + filters << {not: {terms: {field => op_value}}} | ||
166 | + else | ||
167 | + filters << {not: {term: {field => op_value}}} | ||
151 | end | 168 | end |
152 | else | 169 | else |
153 | - filters << {term: {field => value}} | 170 | + range_query = |
171 | + case op | ||
172 | + when :gt | ||
173 | + {from: op_value, include_lower: false} | ||
174 | + when :gte | ||
175 | + {from: op_value, include_lower: true} | ||
176 | + when :lt | ||
177 | + {to: op_value, include_upper: false} | ||
178 | + when :lte | ||
179 | + {to: op_value, include_upper: true} | ||
180 | + else | ||
181 | + raise "Unknown where operator" | ||
182 | + end | ||
183 | + filters << {range: {field => range_query}} | ||
154 | end | 184 | end |
155 | end | 185 | end |
186 | + else | ||
187 | + filters << {term: {field => value}} | ||
156 | end | 188 | end |
157 | - filters | ||
158 | end | 189 | end |
159 | - | ||
160 | - where_filters.call(options[:where]).each do |f| | ||
161 | - type, value = f.first | ||
162 | - filter type, value | ||
163 | end | 190 | end |
191 | + filters | ||
192 | + end | ||
164 | 193 | ||
165 | - # facets | ||
166 | - if options[:facets] | ||
167 | - facets = options[:facets] || {} | ||
168 | - if facets.is_a?(Array) # convert to more advanced syntax | ||
169 | - facets = Hash[ facets.map{|f| [f, {}] } ] | ||
170 | - end | 194 | + # filters |
195 | + filters = where_filters.call(options[:where]) | ||
196 | + if filters.any? | ||
197 | + payload[:filter] = { | ||
198 | + and: filters | ||
199 | + } | ||
200 | + end | ||
171 | 201 | ||
172 | - facets.each do |field, facet_options| | ||
173 | - facet_filters = where_filters.call(facet_options[:where]) | ||
174 | - facet field do | ||
175 | - terms field | ||
176 | - if facet_filters.size == 1 | ||
177 | - type, value = facet_filters.first.first | ||
178 | - facet_filter type, value | ||
179 | - elsif facet_filters.size > 1 | ||
180 | - facet_filter :and, *facet_filters | ||
181 | - end | ||
182 | - end | ||
183 | - end | ||
184 | - end | 202 | + # facets |
203 | + if options[:facets] | ||
204 | + facets = options[:facets] || {} | ||
205 | + if facets.is_a?(Array) # convert to more advanced syntax | ||
206 | + facets = Hash[ facets.map{|f| [f, {}] } ] | ||
185 | end | 207 | end |
186 | 208 | ||
187 | - payload = s.to_hash | 209 | + payload[:facets] = {} |
210 | + facets.each do |field, facet_options| | ||
211 | + payload[:facets][field] = { | ||
212 | + terms: { | ||
213 | + field: field | ||
214 | + } | ||
215 | + } | ||
188 | 216 | ||
189 | - if all | ||
190 | - payload[:query][:custom_filters_score][:query][:match_all] = {} | 217 | + facet_filters = where_filters.call(facet_options[:where]) |
218 | + if facet_filters | ||
219 | + payload[:facets][field][:facet_filter] = { | ||
220 | + and: { | ||
221 | + filters: facet_filters | ||
222 | + } | ||
223 | + } | ||
224 | + end | ||
225 | + end | ||
191 | end | 226 | end |
192 | 227 | ||
193 | - # An empty array will cause only the _id and _type for each hit to be returned | ||
194 | - # http://www.elasticsearch.org/guide/reference/api/search/fields/ | ||
195 | - payload[:fields] = [] if load | ||
196 | - | ||
197 | # suggestions | 228 | # suggestions |
198 | if options[:suggest] | 229 | if options[:suggest] |
199 | suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s) | 230 | suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s) |
@@ -211,6 +242,10 @@ module Searchkick | @@ -211,6 +242,10 @@ module Searchkick | ||
211 | end | 242 | end |
212 | end | 243 | end |
213 | 244 | ||
245 | + # An empty array will cause only the _id and _type for each hit to be returned | ||
246 | + # http://www.elasticsearch.org/guide/reference/api/search/fields/ | ||
247 | + payload[:fields] = [] if load | ||
248 | + | ||
214 | search = Tire::Search::Search.new(index_name, load: load, payload: payload) | 249 | search = Tire::Search::Search.new(index_name, load: load, payload: payload) |
215 | Searchkick::Results.new(search.json, search.options.merge(term: term)) | 250 | Searchkick::Results.new(search.json, search.options.merge(term: term)) |
216 | end | 251 | end |
test/boost_test.rb
@@ -25,10 +25,11 @@ class TestBoost < Minitest::Unit::TestCase | @@ -25,10 +25,11 @@ class TestBoost < Minitest::Unit::TestCase | ||
25 | 25 | ||
26 | def test_boost | 26 | def test_boost |
27 | store [ | 27 | store [ |
28 | - {name: "Organic Tomato A"}, | ||
29 | - {name: "Tomato B", orders_count: 10} | 28 | + {name: "Tomato A"}, |
29 | + {name: "Tomato B", orders_count: 10}, | ||
30 | + {name: "Tomato C", orders_count: 100} | ||
30 | ] | 31 | ] |
31 | - assert_order "tomato", ["Tomato B", "Organic Tomato A"], boost: "orders_count" | 32 | + assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost: "orders_count" |
32 | end | 33 | end |
33 | 34 | ||
34 | def test_boost_zero | 35 | def test_boost_zero |