stretchy 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|