groonga-client 0.6.0 → 0.6.5
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/doc/text/news.md +47 -0
- data/groonga-client.gemspec +3 -2
- data/lib/groonga/client/command-line/groonga-client.rb +29 -2
- data/lib/groonga/client/protocol/http/synchronous.rb +45 -14
- data/lib/groonga/client/request/select.rb +1 -1
- data/lib/groonga/client/response.rb +3 -1
- data/lib/groonga/client/response/base.rb +41 -0
- data/lib/groonga/client/response/drilldownable.rb +85 -0
- data/lib/groonga/client/response/logical-range-filter.rb +52 -0
- data/lib/groonga/client/response/logical-select.rb +28 -0
- data/lib/groonga/client/response/searchable.rb +97 -0
- data/lib/groonga/client/response/select.rb +64 -125
- data/lib/groonga/client/version.rb +2 -2
- data/test/request/test-select.rb +2 -2
- data/test/response/test-select-command-version1.rb +58 -12
- data/test/response/test-select-command-version3.rb +54 -8
- data/test/response/test-select-tsv.rb +26 -4
- data/test/response/test-select-xml.rb +26 -4
- metadata +47 -30
@@ -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-
|
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
|
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 =
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
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
|
-
|
291
|
-
|
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
|
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
|
-
|
298
|
-
|
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
|
-
|
333
|
-
|
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
|
-
|
342
|
-
|
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
|
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-
|
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.
|
19
|
+
VERSION = "0.6.5"
|
20
20
|
end
|
21
21
|
end
|
data/test/request/test-select.rb
CHANGED
@@ -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-
|
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" =>
|
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
|
-
[
|
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
|
-
[
|
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
|
-
|
367
|
-
|
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
|
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
|
-
|
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
|