stretchy 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/stretchy/boosts/base.rb +2 -0
- data/lib/stretchy/boosts/filter_boost.rb +4 -4
- data/lib/stretchy/boosts/random_boost.rb +2 -2
- data/lib/stretchy/builders/boost_builder.rb +36 -0
- data/lib/stretchy/builders/match_builder.rb +44 -0
- data/lib/stretchy/builders/where_builder.rb +126 -0
- data/lib/stretchy/clauses/base.rb +95 -0
- data/lib/stretchy/clauses/boost_clause.rb +50 -0
- data/lib/stretchy/clauses/boost_match_clause.rb +31 -0
- data/lib/stretchy/clauses/boost_where_clause.rb +44 -0
- data/lib/stretchy/clauses/match_clause.rb +57 -0
- data/lib/stretchy/clauses/where_clause.rb +113 -0
- data/lib/stretchy/filters/and_filter.rb +6 -2
- data/lib/stretchy/filters/bool_filter.rb +8 -7
- data/lib/stretchy/filters/exists_filter.rb +3 -0
- data/lib/stretchy/filters/geo_filter.rb +9 -9
- data/lib/stretchy/filters/range_filter.rb +8 -6
- data/lib/stretchy/filters/terms_filter.rb +17 -9
- data/lib/stretchy/queries/bool_query.rb +21 -0
- data/lib/stretchy/queries/filtered_query.rb +4 -3
- data/lib/stretchy/queries/function_score_query.rb +1 -1
- data/lib/stretchy/queries/match_query.rb +12 -5
- data/lib/stretchy/results/base.rb +45 -0
- data/lib/stretchy/results/null_results.rb +22 -0
- data/lib/stretchy/utils/contract.rb +16 -1
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +12 -3
- data/stretchy.gemspec +6 -4
- metadata +42 -5
- data/lib/stretchy/null_query.rb +0 -30
- data/lib/stretchy/query.rb +0 -241
- data/lib/stretchy/request_body.rb +0 -67
data/lib/stretchy/query.rb
DELETED
@@ -1,241 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
class Query
|
3
|
-
|
4
|
-
DEFAULT_LIMIT = 40
|
5
|
-
|
6
|
-
def initialize(options = {})
|
7
|
-
@offset = options[:offset] || 0
|
8
|
-
@limit = options[:limit] || DEFAULT_LIMIT
|
9
|
-
@match = options[:match]
|
10
|
-
@filters = options[:filters]
|
11
|
-
@not_filters = options[:not_filters]
|
12
|
-
@boosts = options[:boosts]
|
13
|
-
@type = options[:type] || 'documents'
|
14
|
-
@index = options[:index] || Stretchy.index_name
|
15
|
-
@explain = options[:explain]
|
16
|
-
end
|
17
|
-
|
18
|
-
def clone_with(options)
|
19
|
-
filters = [@filters, options[:filters], options[:filter]].flatten.compact
|
20
|
-
not_filters = [@not_filters, options[:not_filters], options[:not_filter]].flatten.compact
|
21
|
-
boosts = [@boosts, options[:boosts], options[:boost]].flatten.compact
|
22
|
-
|
23
|
-
self.class.new(
|
24
|
-
type: @type,
|
25
|
-
index: @index,
|
26
|
-
explain: options[:explain] || @explain,
|
27
|
-
offset: options[:offset] || @offset,
|
28
|
-
limit: options[:limit] || @limit,
|
29
|
-
match: options[:match] || @match,
|
30
|
-
filters: filters,
|
31
|
-
not_filters: not_filters,
|
32
|
-
boosts: boosts
|
33
|
-
)
|
34
|
-
end
|
35
|
-
|
36
|
-
def offset(num)
|
37
|
-
clone_with(offset: num)
|
38
|
-
end
|
39
|
-
|
40
|
-
def limit(num)
|
41
|
-
clone_with(limit: num)
|
42
|
-
end
|
43
|
-
|
44
|
-
def explain
|
45
|
-
clone_with(explain: true)
|
46
|
-
end
|
47
|
-
|
48
|
-
def where(options = {})
|
49
|
-
return self if options.empty?
|
50
|
-
filters, not_filters = where_clause(options)
|
51
|
-
clone_with(filters: filters, not_filters: not_filters)
|
52
|
-
end
|
53
|
-
|
54
|
-
def not_where(options = {})
|
55
|
-
return self if options.empty?
|
56
|
-
# reverse the order returned here, since we're doing not_where
|
57
|
-
not_filters, filters = where_clause(options)
|
58
|
-
clone_with(filters: filters, not_filters: not_filters)
|
59
|
-
end
|
60
|
-
|
61
|
-
def boost_where(options = {})
|
62
|
-
return self if options.empty?
|
63
|
-
weight = options.delete(:weight) || 1.2
|
64
|
-
|
65
|
-
boosts = options.map do |field, values|
|
66
|
-
filter = nil
|
67
|
-
if values.nil?
|
68
|
-
filter = Filters::NotFilter.new(Filters::ExistsFilter.new(field))
|
69
|
-
else
|
70
|
-
filter = Filters::TermsFilter.new(field: field, values: Array(values))
|
71
|
-
end
|
72
|
-
Boosts::FilterBoost.new(filter: filter, weight: weight)
|
73
|
-
end
|
74
|
-
clone_with(boosts: boosts)
|
75
|
-
end
|
76
|
-
|
77
|
-
def boost_not_where(options = {})
|
78
|
-
return self if options.empty?
|
79
|
-
weight = options.delete(:weight) || 1.2
|
80
|
-
|
81
|
-
boosts = []
|
82
|
-
options.each do |field, values|
|
83
|
-
filter = nil
|
84
|
-
if values.nil?
|
85
|
-
filter = Filters::ExistsFilter.new(field)
|
86
|
-
elsif values.is_a?(Array)
|
87
|
-
filter = NotFilter.new(Filters::TermsFilter.new(field: field, values: Array(values)))
|
88
|
-
end
|
89
|
-
Boosts::FilterBoost.new(filter: filter)
|
90
|
-
end
|
91
|
-
clone_with(boosts: boosts)
|
92
|
-
end
|
93
|
-
|
94
|
-
def range(field:, min: nil, max: nil)
|
95
|
-
return self unless min || max
|
96
|
-
clone_with(filter: Filters::RangeFilter.new(
|
97
|
-
field: field,
|
98
|
-
min: min,
|
99
|
-
max: max
|
100
|
-
))
|
101
|
-
end
|
102
|
-
|
103
|
-
def not_range(field:, min: nil, max: nil)
|
104
|
-
return self unless min || max
|
105
|
-
clone_with(not_filter: Filters::RangeFilter.new(
|
106
|
-
field: field,
|
107
|
-
min: min,
|
108
|
-
max: max
|
109
|
-
))
|
110
|
-
end
|
111
|
-
|
112
|
-
def boost_range(field:, min: nil, max: nil, weight: 1.2)
|
113
|
-
return self unless min || max
|
114
|
-
clone_with(boost: Boosts::Boost.new(
|
115
|
-
filter: Filters::RangeFilter.new(
|
116
|
-
field: field,
|
117
|
-
min: min,
|
118
|
-
max: max
|
119
|
-
),
|
120
|
-
weight: weight
|
121
|
-
))
|
122
|
-
end
|
123
|
-
|
124
|
-
def geo(field: 'coords', distance: '50km', lat:, lng:)
|
125
|
-
clone_with(filter: Filters::GeoFilter.new(
|
126
|
-
field: field,
|
127
|
-
distance: distance,
|
128
|
-
lat: lat,
|
129
|
-
lng: lng
|
130
|
-
))
|
131
|
-
end
|
132
|
-
|
133
|
-
def not_geo(field: 'coords', distance: '50km', lat:, lng:)
|
134
|
-
clone_with(not_filter: Filters::GeoFilter.new(
|
135
|
-
field: field,
|
136
|
-
distance: distance,
|
137
|
-
lat: lat,
|
138
|
-
lng: lng
|
139
|
-
))
|
140
|
-
end
|
141
|
-
|
142
|
-
def boost_geo(options = {})
|
143
|
-
return self if options.empty?
|
144
|
-
clone_with(boost: Boosts::GeoBoost.new(options))
|
145
|
-
end
|
146
|
-
|
147
|
-
def match(string, options = {})
|
148
|
-
return self if string.nil? || string.empty?
|
149
|
-
field = options[:field] || '_all'
|
150
|
-
operator = options[:operator] || 'and'
|
151
|
-
clone_with(match: Queries::MatchQuery.new(string, field: field, operator: operator))
|
152
|
-
end
|
153
|
-
|
154
|
-
def not_match(string, options = {})
|
155
|
-
field = options[:field] || '_all'
|
156
|
-
operator = options[:operator] || 'and'
|
157
|
-
clone_with(not_filter: Filters::QueryFilter.new(
|
158
|
-
Queries::MatchQuery.new(string, field: field, operator: operator)
|
159
|
-
))
|
160
|
-
end
|
161
|
-
|
162
|
-
def boost_random(seed)
|
163
|
-
clone_with(boost: Boosts::RandomBoost.new(seed))
|
164
|
-
end
|
165
|
-
|
166
|
-
def request
|
167
|
-
@request ||= RequestBody.new(
|
168
|
-
match: @match,
|
169
|
-
filters: @filters,
|
170
|
-
not_filters: @not_filters,
|
171
|
-
boosts: @boosts,
|
172
|
-
limit: @limit,
|
173
|
-
offset: @offset,
|
174
|
-
explain: @explain
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
def response
|
179
|
-
@response = Stretchy.search(type: @type, body: request.to_search)
|
180
|
-
# caution: huuuuge logs, but pretty helpful
|
181
|
-
# Rails.logger.debug(Colorize.purple(JSON.pretty_generate(@response)))
|
182
|
-
@response
|
183
|
-
end
|
184
|
-
|
185
|
-
def id_response
|
186
|
-
@id_response ||= Stretchy.search(type: @type, body: request.to_search, fields: [])
|
187
|
-
# caution: huuuuge logs, but pretty helpful
|
188
|
-
# Rails.logger.debug(Colorize.purple(JSON.pretty_generate(@id_response)))
|
189
|
-
@id_response
|
190
|
-
end
|
191
|
-
|
192
|
-
def results
|
193
|
-
@results ||= response['hits']['hits'].map do |hit|
|
194
|
-
hit['_source'].merge(
|
195
|
-
'_id' => hit['_id'],
|
196
|
-
'_type' => hit['_type'],
|
197
|
-
'_index' => hit['_index']
|
198
|
-
)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def ids
|
203
|
-
@ids ||= result_metadata('hits', 'hits').map do |hit|
|
204
|
-
hit['_id'].to_i == 0 ? hit['_id'] : hit['_id'].to_i
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def shards
|
209
|
-
@shards ||= result_metadata('shards')
|
210
|
-
end
|
211
|
-
|
212
|
-
def total
|
213
|
-
@total ||= result_metadata('hits', 'total')
|
214
|
-
end
|
215
|
-
|
216
|
-
private
|
217
|
-
def result_metadata(*args)
|
218
|
-
if @response
|
219
|
-
args.reduce(@response){|json, field| json.nil? ? nil : json[field] }
|
220
|
-
else
|
221
|
-
args.reduce(id_response){|json, field| json.nil? ? nil : json[field] }
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def where_clause(options = {})
|
226
|
-
return [[], []] if options.empty?
|
227
|
-
filters = []
|
228
|
-
not_filters = []
|
229
|
-
|
230
|
-
options.each do |field, values|
|
231
|
-
if values.nil?
|
232
|
-
not_filters << Filters::ExistsFilter.new(field)
|
233
|
-
else
|
234
|
-
filters << Filters::TermsFilter.new(field: field, values: Array(values))
|
235
|
-
end
|
236
|
-
end
|
237
|
-
[filters, not_filters]
|
238
|
-
end
|
239
|
-
|
240
|
-
end
|
241
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
class RequestBody
|
3
|
-
|
4
|
-
def initialize(options = {})
|
5
|
-
@json = {}
|
6
|
-
@match = options[:match]
|
7
|
-
@filters = Array(options[:filters])
|
8
|
-
@not_filters = Array(options[:not_filters])
|
9
|
-
@boosts = Array(options[:boosts])
|
10
|
-
@offset = options[:offset] || 0
|
11
|
-
@limit = options[:limit] || Query::DEFAULT_LIMIT
|
12
|
-
@explain = options[:explain]
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_search
|
16
|
-
return @json unless @json.empty?
|
17
|
-
|
18
|
-
query = @match || Queries::MatchAllQuery.new
|
19
|
-
|
20
|
-
if @filters.any? && @not_filters.any?
|
21
|
-
query = Queries::FilteredQuery.new(
|
22
|
-
query: query,
|
23
|
-
filter: Filters::BoolFilter.new(
|
24
|
-
must: @filters,
|
25
|
-
must_not: @not_filters
|
26
|
-
)
|
27
|
-
)
|
28
|
-
elsif @filters.any?
|
29
|
-
if @filters.count == 1
|
30
|
-
query = Queries::FilteredQuery.new(
|
31
|
-
query: query,
|
32
|
-
filter: @filters.first
|
33
|
-
)
|
34
|
-
else
|
35
|
-
query = Queries::FilteredQuery.new(
|
36
|
-
query: query,
|
37
|
-
filter: Filters::AndFilter.new(@filters)
|
38
|
-
)
|
39
|
-
end
|
40
|
-
elsif @not_filters.any?
|
41
|
-
query = Queries::FilteredQuery.new(
|
42
|
-
query: query,
|
43
|
-
filter: Filters::NotFilter.new(@not_filters)
|
44
|
-
)
|
45
|
-
end
|
46
|
-
|
47
|
-
if @boosts.any?
|
48
|
-
query = Queries::FunctionScoreQuery.new(
|
49
|
-
query: query,
|
50
|
-
functions: @boosts,
|
51
|
-
score_mode: 'sum',
|
52
|
-
boost_mode: 'max'
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
@json = {}
|
57
|
-
@json[:query] = query.to_search
|
58
|
-
@json[:from] = @offset
|
59
|
-
@json[:size] = @limit
|
60
|
-
@json[:explain] = @explain if @explain
|
61
|
-
|
62
|
-
# not a ton of output, usually worth having
|
63
|
-
# puts "Generated elastic query: #{JSON.pretty_generate(@json)}"
|
64
|
-
@json
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|