groonga-client 0.5.8 → 0.6.3
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 +5 -5
- data/doc/text/news.md +54 -0
- data/groonga-client.gemspec +2 -2
- data/lib/groonga/client.rb +15 -3
- data/lib/groonga/client/command-line/groonga-client.rb +278 -4
- data/lib/groonga/client/protocol/file.rb +76 -0
- data/lib/groonga/client/protocol/http/path-resolvable.rb +6 -1
- data/lib/groonga/client/protocol/http/synchronous.rb +45 -14
- data/lib/groonga/client/response.rb +3 -1
- data/lib/groonga/client/response/base.rb +93 -4
- data/lib/groonga/client/response/drilldownable.rb +85 -0
- data/lib/groonga/client/response/error.rb +42 -2
- 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 +136 -125
- data/lib/groonga/client/version.rb +2 -2
- data/test/response/helper.rb +5 -0
- data/test/response/test-base.rb +14 -1
- data/test/response/test-error.rb +1 -1
- 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 +149 -0
- data/test/response/test-select-xml.rb +26 -4
- data/test/test-client.rb +2 -2
- metadata +35 -29
@@ -0,0 +1,52 @@
|
|
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/base"
|
18
|
+
require "groonga/client/response/searchable"
|
19
|
+
|
20
|
+
module Groonga
|
21
|
+
class Client
|
22
|
+
module Response
|
23
|
+
class LogicalRangeFilter < Base
|
24
|
+
Response.register("logical_range_filter", self)
|
25
|
+
|
26
|
+
include Searchable
|
27
|
+
|
28
|
+
attr_accessor :records
|
29
|
+
|
30
|
+
def body=(body)
|
31
|
+
super(body)
|
32
|
+
parse_body(body)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def parse_body(body)
|
37
|
+
if body.is_a?(::Array)
|
38
|
+
@raw_columns, *@raw_records = body.first
|
39
|
+
@raw_records ||= []
|
40
|
+
@records = parse_records(raw_columns, raw_records)
|
41
|
+
else
|
42
|
+
@raw_columns = body["columns"]
|
43
|
+
@raw_records = body["records"] || []
|
44
|
+
end
|
45
|
+
@records = parse_records(@raw_columns, @raw_records)
|
46
|
+
body
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -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
|
@@ -100,25 +102,88 @@ module Groonga
|
|
100
102
|
|
101
103
|
drilldowns
|
102
104
|
end
|
105
|
+
|
106
|
+
def parse_tsv_body(tsv)
|
107
|
+
record_sets = []
|
108
|
+
|
109
|
+
n_hits = parse_tsv_n_hits(tsv.shift)
|
110
|
+
columns = parse_tsv_columns(tsv.shift)
|
111
|
+
records = []
|
112
|
+
loop do
|
113
|
+
row = tsv.shift
|
114
|
+
break if row.size == 1 and row[0] == "END"
|
115
|
+
if (row.size % 4).zero? and row[0] == "[" and row[-1] == "]"
|
116
|
+
next_n_hits_row = records.pop
|
117
|
+
record_sets << [
|
118
|
+
[n_hits],
|
119
|
+
columns,
|
120
|
+
*records,
|
121
|
+
]
|
122
|
+
n_hits = parse_tsv_n_hits(next_n_hits_row)
|
123
|
+
columns = parse_tsv_columns(row)
|
124
|
+
records = []
|
125
|
+
next
|
126
|
+
end
|
127
|
+
records << parse_tsv_record(row)
|
128
|
+
end
|
129
|
+
|
130
|
+
record_sets << [
|
131
|
+
[n_hits],
|
132
|
+
columns,
|
133
|
+
*records,
|
134
|
+
]
|
135
|
+
record_sets
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_tsv_n_hits(row)
|
139
|
+
Integer(row[0], 10)
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse_tsv_columns(row)
|
143
|
+
columns = []
|
144
|
+
column = nil
|
145
|
+
row.each do |value|
|
146
|
+
case value
|
147
|
+
when "["
|
148
|
+
column = []
|
149
|
+
when "]"
|
150
|
+
columns << column
|
151
|
+
else
|
152
|
+
column << value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
columns
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_tsv_record(row)
|
159
|
+
record = []
|
160
|
+
column_value = nil
|
161
|
+
row.each do |value|
|
162
|
+
case value
|
163
|
+
when "["
|
164
|
+
column_value = []
|
165
|
+
when "]"
|
166
|
+
record << column_value
|
167
|
+
else
|
168
|
+
if column_value
|
169
|
+
column_value << value
|
170
|
+
else
|
171
|
+
record << value
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
record
|
176
|
+
end
|
103
177
|
end
|
104
178
|
|
105
|
-
include
|
179
|
+
include Drilldownable
|
180
|
+
include Searchable
|
106
181
|
|
107
182
|
# @return [Integer] The number of records that match againt
|
108
183
|
# a search condition.
|
109
184
|
attr_accessor :n_hits
|
110
185
|
# For Kaminari
|
111
186
|
alias_method :total_count, :n_hits
|
112
|
-
attr_accessor :records
|
113
|
-
|
114
|
-
# @return [::Array<Groonga::Client::Response::Select::Drilldown>,
|
115
|
-
# ::Hash<String, Groonga::Client::Response::Select::Drilldown>]
|
116
|
-
# If labeled drilldowns are used or command version 3 or
|
117
|
-
# later is used, `{"label1" => drilldown1, "label2" => drilldown2}`
|
118
|
-
# is returned since 0.3.1.
|
119
|
-
#
|
120
|
-
# Otherwise, `[drilldown1, drilldown2]` is returned.
|
121
|
-
attr_accessor :drilldowns
|
122
187
|
|
123
188
|
# @return [::Hash<String, Groonga::Client::Response::Select::Slice>]
|
124
189
|
#
|
@@ -130,29 +195,11 @@ module Groonga
|
|
130
195
|
parse_body(body)
|
131
196
|
end
|
132
197
|
|
133
|
-
# For Kaminari
|
134
|
-
def limit_value
|
135
|
-
(@command[:limit] || 10).to_i
|
136
|
-
end
|
137
|
-
|
138
|
-
# For Kaminari
|
139
|
-
def offset_value
|
140
|
-
(@command[:offset] || 0).to_i
|
141
|
-
end
|
142
|
-
|
143
|
-
# For Kaminari
|
144
|
-
def size
|
145
|
-
records.size
|
146
|
-
end
|
147
|
-
|
148
|
-
def each(&block)
|
149
|
-
records.each(&block)
|
150
|
-
end
|
151
|
-
|
152
198
|
private
|
153
199
|
def parse_body(body)
|
154
200
|
if body.is_a?(::Array)
|
155
|
-
@n_hits, @records =
|
201
|
+
@n_hits, @raw_columns, @raw_records, @records =
|
202
|
+
parse_record_set_v1(body.first)
|
156
203
|
if @command.slices.empty?
|
157
204
|
raw_slices = nil
|
158
205
|
raw_drilldowns = body[1..-1]
|
@@ -160,105 +207,74 @@ module Groonga
|
|
160
207
|
raw_slices, *raw_drilldowns = body[1..-1]
|
161
208
|
end
|
162
209
|
@slices = parse_slices_v1(raw_slices)
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
body
|
170
|
-
end
|
171
|
-
|
172
|
-
def parse_records(raw_columns, raw_records)
|
173
|
-
column_names = {}
|
174
|
-
columns = raw_columns.collect do |column|
|
175
|
-
if column.is_a?(::Array)
|
176
|
-
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])
|
177
215
|
else
|
178
|
-
|
179
|
-
type = column["type"]
|
180
|
-
end
|
181
|
-
base_column_name = name
|
182
|
-
suffix = 2
|
183
|
-
while column_names.key?(name)
|
184
|
-
name = "#{base_column_name}#{suffix}"
|
185
|
-
suffix += 1
|
186
|
-
end
|
187
|
-
column_names[name] = true
|
188
|
-
[name, type]
|
189
|
-
end
|
190
|
-
|
191
|
-
(raw_records || []).collect do |raw_record|
|
192
|
-
record = Record.new
|
193
|
-
columns.each_with_index do |(name, type), i|
|
194
|
-
record[name] = convert_value(raw_record[i], type)
|
195
|
-
end
|
196
|
-
record
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def convert_value(value, type)
|
201
|
-
case value
|
202
|
-
when ::Array
|
203
|
-
value.collect do |element|
|
204
|
-
convert_value(element, type)
|
216
|
+
@drilldowns = parse_drilldowns(drilldown_keys, raw_drilldowns)
|
205
217
|
end
|
206
218
|
else
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
212
231
|
end
|
232
|
+
@drilldowns = parse_labeled_drilldowns(labeled_drilldowns,
|
233
|
+
body["drilldowns"])
|
234
|
+
@slices = parse_slices_v3(body["slices"])
|
213
235
|
end
|
236
|
+
body
|
214
237
|
end
|
215
238
|
|
216
|
-
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] || []
|
217
243
|
[
|
218
|
-
|
219
|
-
|
244
|
+
n_hits,
|
245
|
+
raw_columns,
|
246
|
+
raw_records,
|
247
|
+
parse_records(raw_columns, raw_records),
|
220
248
|
]
|
221
249
|
end
|
222
250
|
|
223
|
-
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"] || []
|
224
255
|
[
|
225
|
-
|
226
|
-
|
256
|
+
n_hits,
|
257
|
+
raw_columns,
|
258
|
+
raw_records,
|
259
|
+
parse_records(raw_columns, raw_records),
|
227
260
|
]
|
228
261
|
end
|
229
262
|
|
230
|
-
def parse_drilldowns_v1(raw_drilldowns)
|
231
|
-
request_drilldowns = @command.drilldowns
|
232
|
-
if request_drilldowns.empty? and !@command.labeled_drilldowns.empty?
|
233
|
-
drilldowns = {}
|
234
|
-
(raw_drilldowns[0] || {}).each do |label, raw_drilldown|
|
235
|
-
n_hits, records = parse_match_records_v1(raw_drilldown)
|
236
|
-
drilldowns[label] = Drilldown.new(label, n_hits, records)
|
237
|
-
end
|
238
|
-
drilldowns
|
239
|
-
else
|
240
|
-
(raw_drilldowns || []).collect.with_index do |raw_drilldown, i|
|
241
|
-
key = request_drilldowns[i]
|
242
|
-
n_hits, records = parse_match_records_v1(raw_drilldown)
|
243
|
-
Drilldown.new(key, n_hits, records)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def parse_drilldowns_v3(raw_drilldowns)
|
249
|
-
drilldowns = {}
|
250
|
-
(raw_drilldowns || {}).each do |key, raw_drilldown|
|
251
|
-
n_hits, records = parse_match_records_v3(raw_drilldown)
|
252
|
-
drilldowns[key] = Drilldown.new(key, n_hits, records)
|
253
|
-
end
|
254
|
-
drilldowns
|
255
|
-
end
|
256
|
-
|
257
263
|
def parse_slices_v1(raw_slices)
|
258
264
|
slices = {}
|
259
265
|
(raw_slices || {}).each do |key, raw_slice|
|
260
|
-
|
261
|
-
|
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)
|
262
278
|
end
|
263
279
|
slices
|
264
280
|
end
|
@@ -266,22 +282,17 @@ module Groonga
|
|
266
282
|
def parse_slices_v3(raw_slices)
|
267
283
|
slices = {}
|
268
284
|
(raw_slices || {}).each do |key, raw_slice|
|
269
|
-
|
270
|
-
|
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)
|
271
291
|
end
|
272
292
|
slices
|
273
293
|
end
|
274
294
|
|
275
|
-
class
|
276
|
-
include Hashie::Extensions::MethodAccess
|
277
|
-
end
|
278
|
-
|
279
|
-
class Drilldown < Struct.new(:key, :n_hits, :records)
|
280
|
-
# @deprecated since 0.2.6. Use {#records} instead.
|
281
|
-
alias_method :items, :records
|
282
|
-
end
|
283
|
-
|
284
|
-
class Slice < Struct.new(:key, :n_hits, :records)
|
295
|
+
class Slice < Struct.new(:key, :n_hits, :records, :drilldowns)
|
285
296
|
end
|
286
297
|
end
|
287
298
|
end
|