redisearch-rb 0.2.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1184cc3821b5bee90dcb18d41ad3b6ea9a93b750
4
- data.tar.gz: 11ac50692f0cfc629e4283baa588e20cba18d854
3
+ metadata.gz: ea52a309430c3f6fbed50c64e43e8a6158606584
4
+ data.tar.gz: 0bcfe47c805b649f14cefed4772526f7004f2173
5
5
  SHA512:
6
- metadata.gz: c0e51193c7b3d7989f973472e5cdc4335f3ed428fa73c56ddbefa271023c053c85107639fc6f05bf0dee00820e4eb729cea3782ab7f0d62cb197d3587e7da624
7
- data.tar.gz: ab05d37fe0469216d05dff6fa0223dd7530832b0a945226352a67a270a2e5239376111418793b7ccff8c2245b68916d46cc71fcba486c8a3f9b48c96d9b30904
6
+ metadata.gz: c3d38b1b71012fa9acd1bb7d14a7e71db16c7d840a3ec934f93f1167b5fb4e86440fc6be916ab17bfe77757b1f4617d2f25d92b490ef3eeed734177edf18605a
7
+ data.tar.gz: 2f80306e5c404618a47ed90aee72c42e77ee343559f2ce54539ef4f74e61d575f3a851761c22ef6520994bde9a04d9ff84269da2da0560efa4d98f0f23d26666
@@ -14,7 +14,7 @@ before_install:
14
14
  - git clone --depth 1 https://github.com/RedisLabsModules/RediSearch.git
15
15
  - cd RediSearch
16
16
  - git fetch && git fetch --tags
17
- - git checkout v1.0.4
17
+ - git checkout v1.2.0
18
18
  - make all
19
19
  - cd ..
20
20
 
@@ -12,15 +12,24 @@ class RediSearch
12
12
  #
13
13
  # { verbatim: true, withscores: true, withsortkey: false }
14
14
 
15
- CREATE_OPTIONS_FLAGS = [:nooffsets, :nofreqs, :nohl, :nofields]
16
- ADD_OPTIONS_FLAGS = [:nosave, :replace, :partial]
17
- SEARCH_OPTIONS_FLAGS = [:nocontent, :verbatim, :nostopwords, :withscores, :withsortkeys]
15
+ OPTIONS_FLAGS = {
16
+ add: [:nosave, :replace, :partial],
17
+ create: [:nooffsets, :nofreqs, :nohl, :nofields],
18
+ del: [:dd],
19
+ drop: [:keepdocs],
20
+ search: [:nocontent, :verbatim, :nostopwords, :withscores, :withsortkeys],
21
+ sugadd: [:incr],
22
+ sugget: [:fuzzy, :withscores],
23
+ }
18
24
 
19
25
  # Params options need an array with the values for the option
20
26
  # { limit: ['0', '50'], sortby: ['year', 'desc'], return: ['2', 'title', 'year'] }
21
- CREATE_OPTIONS_PARAMS = [:stopwords]
22
- ADD_OPTIONS_PARAMS = [:language]
23
- SEARCH_OPTIONS_PARAMS = [:filter, :return, :infields, :inkeys, :slop, :scorer, :sortby, :limit]
27
+ OPTIONS_PARAMS = {
28
+ add: [:language, :payload],
29
+ create: [:stopwords],
30
+ search: [:filter, :return, :infields, :inkeys, :slop, :scorer, :sortby, :limit, :payload],
31
+ sugget: [:max],
32
+ }
24
33
 
25
34
  # Create RediSearch client instance
26
35
  #
@@ -54,8 +63,8 @@ class RediSearch
54
63
  #
55
64
  # See http://redisearch.io/Commands/#ftdrop
56
65
  # @return [String] "OK" on success
57
- def drop_index
58
- call(ft_drop)
66
+ def drop_index(opts = {})
67
+ call(ft_drop(opts))
59
68
  end
60
69
 
61
70
  # Add a single doc to the index, with the given `doc_id` and `fields`
@@ -122,27 +131,75 @@ class RediSearch
122
131
  #
123
132
  # @param [String] doc_id id assigned to the document
124
133
  # @return [int] 1 if the document was in the index, or 0 if not.
125
- def delete_by_id(doc_id)
126
- call(ft_del(doc_id))
134
+ def delete_by_id(doc_id, opts = {})
135
+ call(ft_del(doc_id, opts))
127
136
  end
128
137
 
129
- # Deletes all documents returned by the query
138
+ # Deletes all documents matching the query
130
139
  #
131
140
  # @param [String] query in the same format as used in `search`
132
141
  # @param [Hash] opts options for the query, same as in `search`
133
142
  # @return [int] count of documents deleted
134
143
  def delete_by_query(query, opts = {})
135
144
  call(ft_search(query, opts.merge(nocontent: true)))[1..-1].map do |doc_id|
136
- call(ft_del(doc_id))
145
+ call(ft_del(doc_id, opts))
137
146
  end.sum
138
147
  end
139
148
 
140
- # Execute arbitrary command. Only RediSearch commands are allowed
149
+ # Adds a string to an auto-complete suggestion dictionary.
150
+ #
151
+ # See https://oss.redislabs.com/redisearch/Commands/#ftsugadd
152
+ #
153
+ # @param [String] dict_name the key used to store the dictionary
154
+ # @param [String] content the string that is going to be indexed
155
+ # @param [Hash] opts optional parameters
156
+ # @return [int] current size of the dictionary
157
+ def autocomplete_add(dict_name, content, score = 1.0, opts = {})
158
+ call(ft_sugadd(dict_name, content, score, opts))
159
+ end
160
+
161
+ # Gets completion suggestions for a prefix.
162
+ #
163
+ # See https://oss.redislabs.com/redisearch/Commands/#ftsugadd
164
+ #
165
+ # @param [String] dict_name the key used to store the dictionary
166
+ # @param [String] prefix the prefix to search / complete
167
+ # @param [Hash] opts optional parameters
168
+ # @return [Array] a list of the top suggestions matching the prefix,
169
+ # optionally with score after each entry
170
+ def autocomplete_get(dict_name, prefix, opts = {})
171
+ call(ft_sugget(dict_name, prefix, opts))
172
+ end
173
+
174
+ # Deletes a string from an auto-complete suggestion dictionary.
175
+ #
176
+ # See https://oss.redislabs.com/redisearch/Commands/#ftsugdel
177
+ #
178
+ # @param [String] dict_name the key used to store the dictionary
179
+ # @param [String] content the string that is going to be deleted
180
+ # @param [Hash] opts optional parameters
181
+ # @return [int] 1 if the string was found and deleted, 0 otherwise
182
+ def autocomplete_del(dict_name, content)
183
+ call(ft_sugdel(dict_name, content))
184
+ end
185
+
186
+ # Gets the current size of an auto-complete suggestion dictionary.
187
+ #
188
+ # See https://oss.redislabs.com/redisearch/Commands/#ftsugdel
189
+ #
190
+ # @param [String] dict_name the key used to store the dictionary
191
+ # @return [int] current size of the dictionary
192
+ def autocomplete_len(dict_name)
193
+ call(ft_suglen(dict_name))
194
+ end
195
+
196
+ # Execute arbitrary command in redisearch index
197
+ # Only RediSearch commands are allowed
141
198
  #
142
199
  # @param [Array] command
143
200
  # @return [mixed] The output returned by redis
144
201
  def call(command)
145
- raise ArgumentError.new("#{command&.first} is not a RediSearch command") unless valid_command?(command)
202
+ raise ArgumentError.new("unknown/unsupported command '#{command.first}'") unless valid_command?(command.first)
146
203
  @redis.with_reconnect { @redis.call(command.flatten) }
147
204
  end
148
205
 
@@ -161,11 +218,11 @@ class RediSearch
161
218
  end
162
219
 
163
220
  def ft_create(schema, opts)
164
- ['FT.CREATE', @idx_name , *create_options(opts), 'SCHEMA', *schema]
221
+ ['FT.CREATE', @idx_name , *serialize_options(opts, :create), 'SCHEMA', *schema]
165
222
  end
166
223
 
167
- def ft_drop
168
- ['FT.DROP', @idx_name]
224
+ def ft_drop(opts)
225
+ ['FT.DROP', @idx_name, *serialize_options(opts, :drop)]
169
226
  end
170
227
 
171
228
  def ft_info
@@ -173,15 +230,15 @@ class RediSearch
173
230
  end
174
231
 
175
232
  def ft_add(doc_id, fields, opts = {}, weight = nil)
176
- ['FT.ADD', @idx_name , doc_id, weight || DEFAULT_WEIGHT, *add_options(opts), 'FIELDS', *fields]
233
+ ['FT.ADD', @idx_name , doc_id, weight || DEFAULT_WEIGHT, *serialize_options(opts, :add), 'FIELDS', *fields]
177
234
  end
178
235
 
179
236
  def ft_add_hash(doc_id, opts = {}, weight = nil)
180
- ['FT.ADDHASH', @idx_name , doc_id, weight || DEFAULT_WEIGHT, *add_options(opts)]
237
+ ['FT.ADDHASH', @idx_name , doc_id, weight || DEFAULT_WEIGHT, *serialize_options(opts, :add)]
181
238
  end
182
239
 
183
240
  def ft_search(query, opts)
184
- ['FT.SEARCH', @idx_name, *query, *search_options(opts)].flatten
241
+ ['FT.SEARCH', @idx_name, *query, *serialize_options(opts, :search)].flatten
185
242
  end
186
243
 
187
244
  def ft_get(doc_id)
@@ -192,40 +249,59 @@ class RediSearch
192
249
  ['FT.MGET', @idx_name , *doc_ids]
193
250
  end
194
251
 
195
- def ft_del(doc_id)
196
- ['FT.DEL', @idx_name , doc_id]
252
+ def ft_del(doc_id, opts)
253
+ ['FT.DEL', @idx_name , doc_id, *serialize_options(opts, :del)]
254
+ end
255
+
256
+ def ft_tagvals(field_name)
257
+ ['FT.TAGVALS', @idx_name , field_name]
258
+ end
259
+
260
+ def ft_explain(query, opts)
261
+ ['FT.EXPLAIN', @idx_name, *query, *serialize_options(opts, :search)].flatten
262
+ end
263
+
264
+ def ft_sugadd(dict_name, content, score, opts)
265
+ ['FT.SUGADD', dict_name , content, score, *serialize_options(opts, :sugadd)]
197
266
  end
198
267
 
199
- def create_options(opts = {})
200
- build_options(opts, CREATE_OPTIONS_FLAGS, [])
268
+ def ft_sugdel(dict_name, content)
269
+ ['FT.SUGDEL', dict_name , content]
201
270
  end
202
271
 
203
- def add_options(opts = {})
204
- build_options(opts, ADD_OPTIONS_FLAGS, ADD_OPTIONS_PARAMS)
272
+ def ft_suglen(dict_name)
273
+ ['FT.SUGLEN', dict_name]
205
274
  end
206
275
 
207
- def search_options(opts = {})
208
- build_options(opts, SEARCH_OPTIONS_FLAGS, SEARCH_OPTIONS_PARAMS)
276
+ def ft_sugget(dict_name, prefix,opts)
277
+ ['FT.SUGGET', dict_name , prefix, *serialize_options(opts, :sugget)]
209
278
  end
210
279
 
211
- def build_options(opts, flags_keys, params_keys)
212
- flags_keys.map do |key|
280
+ def serialize_options(opts, method)
281
+ [flags_for_method(opts, method), params_for_method(opts, method)].flatten.compact
282
+ end
283
+
284
+ def flags_for_method(opts, method)
285
+ OPTIONS_FLAGS[method].to_a.map do |key|
213
286
  key.to_s.upcase if opts[key]
214
- end.compact +
215
- params_keys.map do |key|
216
- [key.to_s.upcase, *opts[key]] unless opts[key].nil?
217
- end.compact
287
+ end.compact
288
+ end
289
+
290
+ def params_for_method(opts, method)
291
+ OPTIONS_PARAMS[method].to_a.map do |key|
292
+ [key.to_s.upcase, *opts[key]] unless opts[key].nil?
293
+ end.compact
218
294
  end
219
295
 
220
296
  def build_docs(results, opts = {})
221
297
  return {} if results.nil? || results[0] == 0
222
298
  results.shift
223
299
  score_offset = opts[:withscores] ? 1 : 0
224
- content_offset = score_offset + 1
225
- rows_per_doc = 1 + content_offset
300
+ content_offset = opts[:nocontent] ? 0 : 1
301
+ rows_per_doc = 1 + content_offset + score_offset
226
302
  nr_of_docs = results.size / rows_per_doc
227
303
  (0..nr_of_docs-1).map do |n|
228
- doc = opts[:nocontent] ? {} : Hash[*results[rows_per_doc * n + content_offset]]
304
+ doc = opts[:nocontent] ? {} : Hash[*results[rows_per_doc * n + content_offset + score_offset]]
229
305
  doc['score'] = results[rows_per_doc * n + score_offset] if opts[:withscores]
230
306
  doc['id'] = results[rows_per_doc * n]
231
307
  doc
@@ -233,6 +309,8 @@ class RediSearch
233
309
  end
234
310
 
235
311
  def valid_command?(command)
236
- command[0] =~ /^ft\./i
312
+ %w(FT.CREATE FT.ADD FT.ADDHASH FT.SEARCH FT.DEL FT.DROP FT.GET FT.MGET
313
+ FT.SUGADD FT.SUGGET FT.SUGDEL FT.SUGLEN FT.SYNADD FT.SYNUPDATE FT.SYNDUMP
314
+ FT.INFO FT.AGGREGATE FT.EXPLAIN FT.TAGVALS).include?(command)
237
315
  end
238
316
  end
@@ -1,3 +1,3 @@
1
1
  class RediSearch
2
- VERSION = '0.2.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -37,8 +37,19 @@ class RediSearchTest < Minitest::Test
37
37
 
38
38
  def test_drop_idx
39
39
  assert(@redisearch_client.create_index(@schema))
40
- assert(@redisearch_client.drop_index)
40
+ doc = ['title', 'Lost in translation', 'director', 'Sofia Coppola', 'year', '2004']
41
+ @redisearch_client.add_doc('id_1', doc)
42
+ @redisearch_client.drop_index
41
43
  assert_raises(Redis::CommandError) { @redisearch_client.info }
44
+ assert(@redis_client.hgetall('id_1').empty?)
45
+ end
46
+
47
+ def test_drop_idx_keep_documents
48
+ @redisearch_client.create_index(@schema)
49
+ doc = ['title', 'Lost in translation', 'director', 'Sofia Coppola', 'year', '2004']
50
+ @redisearch_client.add_doc('id_1', doc)
51
+ @redisearch_client.drop_index({ keepdocs: true })
52
+ assert(@redis_client.hgetall('id_1').any?)
42
53
  end
43
54
 
44
55
  def test_add_doc
@@ -175,10 +186,28 @@ class RediSearchTest < Minitest::Test
175
186
  ['id_3', ['title', 'Terminator', 'director', 'James Cameron', 'year', '1984']],
176
187
  ['id_4', ['title', 'Blade Runner', 'director', 'Ridley Scott', 'year', '1982']]]
177
188
  assert(@redisearch_client.add_docs(docs))
178
- assert_equal(1, @redisearch_client.delete_by_id('id_1'))
189
+ assert_equal(1, @redisearch_client.delete_by_id('id_1', { dd: false }))
190
+ assert_equal(1, @redisearch_client.delete_by_id('id_2', { dd: true }))
179
191
  assert_empty(@redisearch_client.search('@title:lost'))
180
192
  assert(@redisearch_client.get_by_id('id_1').any?)
193
+ assert(@redisearch_client.get_by_id('id_2').empty?)
181
194
  assert(@redis_client.del('id_1'))
182
195
  assert(@redisearch_client.get_by_id('id_1').empty?)
183
196
  end
197
+
198
+ def test_auto_complete_suggestions
199
+ dict_name = 'test_suggestions'
200
+ @redisearch_client.autocomplete_add(dict_name, 'foobar', 2.0)
201
+ @redisearch_client.autocomplete_add(dict_name, 'foowoz', 1.0)
202
+ @redisearch_client.autocomplete_add(dict_name, 'foomeh', 0.85)
203
+ @redisearch_client.autocomplete_add(dict_name, 'woowoz', 1.0)
204
+ assert_equal(4, @redisearch_client.autocomplete_len(dict_name))
205
+ results = @redisearch_client.autocomplete_get(dict_name, 'foo')
206
+ assert_equal(3, results.count)
207
+ assert_equal('foobar', results.first)
208
+ assert_equal(4, @redisearch_client.autocomplete_get(dict_name, 'foo', { fuzzy: true }).count)
209
+ assert @redisearch_client.autocomplete_del(dict_name, 'foobar').to_i > 0
210
+ refute @redisearch_client.autocomplete_del(dict_name, 'foozzz').to_i > 0
211
+ assert_equal('foowoz', @redisearch_client.autocomplete_get(dict_name, 'foo').first)
212
+ end
184
213
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redisearch-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Ruiz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-15 00:00:00.000000000 Z
11
+ date: 2018-11-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |-
14
14
  A simple Ruby client library for RediSearch.