groonga-client 0.6.0 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ # Copyright (C) 2019 Sutou Kouhei <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ require "groonga/client/response/select"
18
+
19
+ module Groonga
20
+ class Client
21
+ module Response
22
+ class LogicalSelect < Select
23
+ Response.register("logical_select", self)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,97 @@
1
+ # Copyright (C) 2019 Sutou Kouhei <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ module Groonga
18
+ class Client
19
+ module Response
20
+ module Searchable
21
+ include Enumerable
22
+
23
+ attr_accessor :records
24
+ attr_accessor :raw_columns
25
+ attr_accessor :raw_records
26
+
27
+ # For Kaminari
28
+ def limit_value
29
+ (@command[:limit] || 10).to_i
30
+ end
31
+
32
+ # For Kaminari
33
+ def offset_value
34
+ (@command[:offset] || 0).to_i
35
+ end
36
+
37
+ # For Kaminari
38
+ def size
39
+ records.size
40
+ end
41
+
42
+ def each(&block)
43
+ records.each(&block)
44
+ end
45
+
46
+ private
47
+ def parse_records(raw_columns, raw_records)
48
+ column_names = {}
49
+ columns = raw_columns.collect do |column|
50
+ if column.is_a?(::Array)
51
+ name, type = column
52
+ else
53
+ name = column["name"]
54
+ type = column["type"]
55
+ end
56
+ base_column_name = name
57
+ suffix = 2
58
+ while column_names.key?(name)
59
+ name = "#{base_column_name}#{suffix}"
60
+ suffix += 1
61
+ end
62
+ column_names[name] = true
63
+ [name, type]
64
+ end
65
+
66
+ (raw_records || []).collect do |raw_record|
67
+ record = Record.new
68
+ columns.each_with_index do |(name, type), i|
69
+ record[name] = convert_value(raw_record[i], type)
70
+ end
71
+ record
72
+ end
73
+ end
74
+
75
+ def convert_value(value, type)
76
+ case value
77
+ when ::Array
78
+ value.collect do |element|
79
+ convert_value(element, type)
80
+ end
81
+ else
82
+ case type
83
+ when "Time"
84
+ Time.at(value)
85
+ else
86
+ value
87
+ end
88
+ end
89
+ end
90
+
91
+ class Record < ::Hash
92
+ include Hashie::Extensions::MethodAccess
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2013-2019 Sutou Kouhei <kou@clear-code.com>
2
2
  # Copyright (C) 2013 Kosuke Asami
3
3
  # Copyright (C) 2016 Masafumi Yokoyama <yokoyama@clear-code.com>
4
4
  #
@@ -17,6 +17,8 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
19
  require "groonga/client/response/base"
20
+ require "groonga/client/response/drilldownable"
21
+ require "groonga/client/response/searchable"
20
22
 
21
23
  module Groonga
22
24
  class Client
@@ -174,23 +176,14 @@ module Groonga
174
176
  end
175
177
  end
176
178
 
177
- include Enumerable
179
+ include Drilldownable
180
+ include Searchable
178
181
 
179
182
  # @return [Integer] The number of records that match againt
180
183
  # a search condition.
181
184
  attr_accessor :n_hits
182
185
  # For Kaminari
183
186
  alias_method :total_count, :n_hits
184
- attr_accessor :records
185
-
186
- # @return [::Array<Groonga::Client::Response::Select::Drilldown>,
187
- # ::Hash<String, Groonga::Client::Response::Select::Drilldown>]
188
- # If labeled drilldowns are used or command version 3 or
189
- # later is used, `{"label1" => drilldown1, "label2" => drilldown2}`
190
- # is returned since 0.3.1.
191
- #
192
- # Otherwise, `[drilldown1, drilldown2]` is returned.
193
- attr_accessor :drilldowns
194
187
 
195
188
  # @return [::Hash<String, Groonga::Client::Response::Select::Slice>]
196
189
  #
@@ -202,29 +195,11 @@ module Groonga
202
195
  parse_body(body)
203
196
  end
204
197
 
205
- # For Kaminari
206
- def limit_value
207
- (@command[:limit] || 10).to_i
208
- end
209
-
210
- # For Kaminari
211
- def offset_value
212
- (@command[:offset] || 0).to_i
213
- end
214
-
215
- # For Kaminari
216
- def size
217
- records.size
218
- end
219
-
220
- def each(&block)
221
- records.each(&block)
222
- end
223
-
224
198
  private
225
199
  def parse_body(body)
226
200
  if body.is_a?(::Array)
227
- @n_hits, @records = parse_match_records_v1(body.first)
201
+ @n_hits, @raw_columns, @raw_records, @records =
202
+ parse_record_set_v1(body.first)
228
203
  if @command.slices.empty?
229
204
  raw_slices = nil
230
205
  raw_drilldowns = body[1..-1]
@@ -232,105 +207,74 @@ module Groonga
232
207
  raw_slices, *raw_drilldowns = body[1..-1]
233
208
  end
234
209
  @slices = parse_slices_v1(raw_slices)
235
- @drilldowns = parse_drilldowns_v1(raw_drilldowns)
236
- else
237
- @n_hits, @records = parse_match_records_v3(body)
238
- @drilldowns = parse_drilldowns_v3(body["drilldowns"])
239
- @slices = parse_slices_v3(body["slices"])
240
- end
241
- body
242
- end
243
-
244
- def parse_records(raw_columns, raw_records)
245
- column_names = {}
246
- columns = raw_columns.collect do |column|
247
- if column.is_a?(::Array)
248
- name, type = column
210
+ drilldown_keys = @command.drilldowns
211
+ labeled_drilldowns = @command.labeled_drilldowns
212
+ if drilldown_keys.empty? and !labeled_drilldowns.empty?
213
+ @drilldowns = parse_labeled_drilldowns(labeled_drilldowns,
214
+ raw_drilldowns[0])
249
215
  else
250
- name = column["name"]
251
- type = column["type"]
252
- end
253
- base_column_name = name
254
- suffix = 2
255
- while column_names.key?(name)
256
- name = "#{base_column_name}#{suffix}"
257
- suffix += 1
258
- end
259
- column_names[name] = true
260
- [name, type]
261
- end
262
-
263
- (raw_records || []).collect do |raw_record|
264
- record = Record.new
265
- columns.each_with_index do |(name, type), i|
266
- record[name] = convert_value(raw_record[i], type)
267
- end
268
- record
269
- end
270
- end
271
-
272
- def convert_value(value, type)
273
- case value
274
- when ::Array
275
- value.collect do |element|
276
- convert_value(element, type)
216
+ @drilldowns = parse_drilldowns(drilldown_keys, raw_drilldowns)
277
217
  end
278
218
  else
279
- case type
280
- when "Time"
281
- Time.at(value)
282
- else
283
- value
219
+ @n_hits, @raw_columns, @raw_records, @records =
220
+ parse_record_set_v3(body)
221
+ drilldown_keys = @command.drilldowns
222
+ labeled_drilldowns = @command.labeled_drilldowns
223
+ if labeled_drilldowns.empty?
224
+ drilldown_keys.each do |key|
225
+ labeled_drilldown =
226
+ Groonga::Command::Drilldownable::Drilldown.new
227
+ labeled_drilldown.label = key
228
+ labeled_drilldown.keys = [key]
229
+ labeled_drilldowns[key] = labeled_drilldown
230
+ end
284
231
  end
232
+ @drilldowns = parse_labeled_drilldowns(labeled_drilldowns,
233
+ body["drilldowns"])
234
+ @slices = parse_slices_v3(body["slices"])
285
235
  end
236
+ body
286
237
  end
287
238
 
288
- def parse_match_records_v1(raw_records)
239
+ def parse_record_set_v1(raw_record_set)
240
+ n_hits = raw_record_set.first.first
241
+ raw_columns = raw_record_set[1]
242
+ raw_records = raw_record_set[2..-1] || []
289
243
  [
290
- raw_records.first.first,
291
- parse_records(raw_records[1], raw_records[2..-1]),
244
+ n_hits,
245
+ raw_columns,
246
+ raw_records,
247
+ parse_records(raw_columns, raw_records),
292
248
  ]
293
249
  end
294
250
 
295
- def parse_match_records_v3(raw_records)
251
+ def parse_record_set_v3(raw_record_set)
252
+ n_hits = raw_record_set["n_hits"]
253
+ raw_columns = raw_record_set["columns"]
254
+ raw_records = raw_record_set["records"] || []
296
255
  [
297
- raw_records["n_hits"],
298
- parse_records(raw_records["columns"], raw_records["records"]),
256
+ n_hits,
257
+ raw_columns,
258
+ raw_records,
259
+ parse_records(raw_columns, raw_records),
299
260
  ]
300
261
  end
301
262
 
302
- def parse_drilldowns_v1(raw_drilldowns)
303
- request_drilldowns = @command.drilldowns
304
- if request_drilldowns.empty? and !@command.labeled_drilldowns.empty?
305
- drilldowns = {}
306
- (raw_drilldowns[0] || {}).each do |label, raw_drilldown|
307
- n_hits, records = parse_match_records_v1(raw_drilldown)
308
- drilldowns[label] = Drilldown.new(label, n_hits, records)
309
- end
310
- drilldowns
311
- else
312
- (raw_drilldowns || []).collect.with_index do |raw_drilldown, i|
313
- key = request_drilldowns[i]
314
- n_hits, records = parse_match_records_v1(raw_drilldown)
315
- Drilldown.new(key, n_hits, records)
316
- end
317
- end
318
- end
319
-
320
- def parse_drilldowns_v3(raw_drilldowns)
321
- drilldowns = {}
322
- (raw_drilldowns || {}).each do |key, raw_drilldown|
323
- n_hits, records = parse_match_records_v3(raw_drilldown)
324
- drilldowns[key] = Drilldown.new(key, n_hits, records)
325
- end
326
- drilldowns
327
- end
328
-
329
263
  def parse_slices_v1(raw_slices)
330
264
  slices = {}
331
265
  (raw_slices || {}).each do |key, raw_slice|
332
- n_hits, records = parse_match_records_v1(raw_slice)
333
- slices[key] = Slice.new(key, n_hits, records)
266
+ requested_slice = @command.slices[key]
267
+ if raw_slice.last.is_a?(::Hash)
268
+ raw_drilldowns = raw_slice.last
269
+ raw_slice = raw_slice[0..-2]
270
+ drilldowns =
271
+ parse_labeled_drilldowns(requested_slice.labeled_drilldowns,
272
+ raw_drilldowns)
273
+ else
274
+ drilldowns = {}
275
+ end
276
+ n_hits, _, _, records = parse_record_set_v1(raw_slice)
277
+ slices[key] = Slice.new(key, n_hits, records, drilldowns)
334
278
  end
335
279
  slices
336
280
  end
@@ -338,22 +282,17 @@ module Groonga
338
282
  def parse_slices_v3(raw_slices)
339
283
  slices = {}
340
284
  (raw_slices || {}).each do |key, raw_slice|
341
- n_hits, records = parse_match_records_v3(raw_slice)
342
- slices[key] = Slice.new(key, n_hits, records)
285
+ requested_slice = @command.slices[key]
286
+ n_hits, _, _, records = parse_record_set_v3(raw_slice)
287
+ drilldowns =
288
+ parse_labeled_drilldowns(requested_slice.labeled_drilldowns,
289
+ raw_slice["drilldowns"])
290
+ slices[key] = Slice.new(key, n_hits, records, drilldowns)
343
291
  end
344
292
  slices
345
293
  end
346
294
 
347
- class Record < ::Hash
348
- include Hashie::Extensions::MethodAccess
349
- end
350
-
351
- class Drilldown < Struct.new(:key, :n_hits, :records)
352
- # @deprecated since 0.2.6. Use {#records} instead.
353
- alias_method :items, :records
354
- end
355
-
356
- class Slice < Struct.new(:key, :n_hits, :records)
295
+ class Slice < Struct.new(:key, :n_hits, :records, :drilldowns)
357
296
  end
358
297
  end
359
298
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2018 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2013-2021 Sutou Kouhei <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Groonga
18
18
  class Client
19
- VERSION = "0.6.0"
19
+ VERSION = "0.6.5"
20
20
  end
21
21
  end
@@ -186,8 +186,8 @@ class TestRequestSelect < Test::Unit::TestCase
186
186
  end
187
187
 
188
188
  sub_test_case("#paginate") do
189
- def paginate(*args)
190
- @request.paginate(*args).to_parameters
189
+ def paginate(*args, **kwargs)
190
+ @request.paginate(*args, **kwargs).to_parameters
191
191
  end
192
192
 
193
193
  sub_test_case("page") do
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2013-2020 Sutou Kouhei <kou@clear-code.com>
2
2
  # Copyright (C) 2013 Kosuke Asami
3
3
  # Copyright (C) 2016 Masafumi Yokoyama <yokoyama@clear-code.com>
4
4
  #
@@ -320,12 +320,14 @@ class TestResponseSelectCommandVersion1 < Test::Unit::TestCase
320
320
  class TestSlices < self
321
321
  def setup
322
322
  pair_arguments = {
323
- "slices[groonga].filter" => 'tag @ "groonga"',
323
+ "slices[groonga].filter" => "tag @ 'groonga'",
324
+ "slices[groonga].limit" => "2",
325
+ "slices[groonga].drilldowns[author].keys" => "author",
324
326
  }
325
327
  @command = Groonga::Command::Select.new("select", pair_arguments)
326
328
  @body = [
327
329
  [
328
- [3],
330
+ [4],
329
331
  [
330
332
  [
331
333
  "_id",
@@ -339,10 +341,11 @@ class TestResponseSelectCommandVersion1 < Test::Unit::TestCase
339
341
  [1, "groonga"],
340
342
  [2, "rroonga"],
341
343
  [3, "groonga"],
344
+ [4, "groonga"],
342
345
  ],
343
346
  {
344
347
  "groonga" => [
345
- [2],
348
+ [3],
346
349
  [
347
350
  [
348
351
  "_id",
@@ -355,19 +358,55 @@ class TestResponseSelectCommandVersion1 < Test::Unit::TestCase
355
358
  ],
356
359
  [1, "groonga"],
357
360
  [3, "groonga"],
358
- ]
359
- }
361
+ {
362
+ "author" => [
363
+ [3],
364
+ [
365
+ [
366
+ "_key",
367
+ "ShortText",
368
+ ],
369
+ [
370
+ "_nsubrecs",
371
+ "Int32",
372
+ ],
373
+ ],
374
+ [
375
+ "Alice",
376
+ 1,
377
+ ],
378
+ [
379
+ "Bob",
380
+ 1,
381
+ ],
382
+ [
383
+ "Chris",
384
+ 1,
385
+ ],
386
+ ],
387
+ },
388
+ ],
389
+ },
360
390
  ]
361
391
  end
362
392
 
363
393
  def test_slices
364
394
  assert_equal({
365
- "groonga" => [
366
- {"_id" => 1, "tag" => "groonga"},
367
- {"_id" => 3, "tag" => "groonga"},
368
- ]
395
+ "groonga" => {
396
+ records: [
397
+ {"_id" => 1, "tag" => "groonga"},
398
+ {"_id" => 3, "tag" => "groonga"},
399
+ ],
400
+ drilldowns: {
401
+ "author" => [
402
+ {"_key" => "Alice", "_nsubrecs" => 1},
403
+ {"_key" => "Bob", "_nsubrecs" => 1},
404
+ {"_key" => "Chris", "_nsubrecs" => 1},
405
+ ],
406
+ },
407
+ },
369
408
  },
370
- collect_values(@body, &:records))
409
+ collect_values(@body))
371
410
  end
372
411
 
373
412
  private
@@ -378,7 +417,14 @@ class TestResponseSelectCommandVersion1 < Test::Unit::TestCase
378
417
  def collect_values(body)
379
418
  values = {}
380
419
  slices(body).each do |label, slice|
381
- values[label] = yield(slice)
420
+ drilldowns = {}
421
+ slice.drilldowns.each do |drilldown_label, drilldown|
422
+ drilldowns[drilldown_label] = drilldown.records
423
+ end
424
+ values[label] = {
425
+ records: slice.records,
426
+ drilldowns: drilldowns,
427
+ }
382
428
  end
383
429
  values
384
430
  end