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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29a5323d61e22fd67047fc6adbf076dfca390ef9fcde76e653d1d52ccff00c55
|
4
|
+
data.tar.gz: bcff102d164f18996b61e45e979d63ed4cf812ed1747fe7d454c349eafddbde8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3bff560db004312b66a7f38bcf07e3a4a28b5078d6fabc413e0ee58b816dee31bd022539baab88d2adfbd57fc85ad27e43c339aa282b602e98efd67059c9595
|
7
|
+
data.tar.gz: e943e75c8985175fb96aeeccbe8189c70c6709d4cb4d6635f547b859e27afd2b80abd6ebe6a5c7b108ceeca1be13e1a2c4f1c12126472cd09d489551a0d41cfe
|
data/doc/text/news.md
CHANGED
@@ -1,5 +1,59 @@
|
|
1
1
|
# NEWS
|
2
2
|
|
3
|
+
## 0.6.3 - 2020-06-08
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* `groonga-client`:
|
8
|
+
|
9
|
+
* Added support for `-load-input-type=apache-arrow`.
|
10
|
+
|
11
|
+
* Added `--load-lock-table`.
|
12
|
+
|
13
|
+
* `http`: Added support for debugging by
|
14
|
+
`GROONGA_CLIENT_HTTP_DEBUG=yes` environment variable.
|
15
|
+
|
16
|
+
* Added support for response in Apache Arrow.
|
17
|
+
|
18
|
+
## 0.6.2 - 2019-09-02
|
19
|
+
|
20
|
+
### Improvements
|
21
|
+
|
22
|
+
* `Groonga::Client::Response::LogicalSelect`: Added.
|
23
|
+
|
24
|
+
* `Groonga::Client::Response::Select#raw_columns`: Added.
|
25
|
+
|
26
|
+
* `Groonga::Client::Response::Select#raw_records`: Added.
|
27
|
+
|
28
|
+
* `Groonga::Client::Response::LogicalRangeFilter`: Added.
|
29
|
+
|
30
|
+
## 0.6.1 - 2019-07-27
|
31
|
+
|
32
|
+
### Improvements
|
33
|
+
|
34
|
+
* `Groonga::Client::Request::Select`: Added support for drilldowns
|
35
|
+
in slice.
|
36
|
+
|
37
|
+
## 0.6.0 - 2018-08-30
|
38
|
+
|
39
|
+
### Improvements
|
40
|
+
|
41
|
+
* `groonga-client`:
|
42
|
+
|
43
|
+
* Added `--target-command` option.
|
44
|
+
|
45
|
+
* Added `--target-table` option.
|
46
|
+
|
47
|
+
* Added `--target-column` option.
|
48
|
+
|
49
|
+
* Added support for JSONP.
|
50
|
+
|
51
|
+
## 0.5.9 - 2018-07-13
|
52
|
+
|
53
|
+
### Improvements
|
54
|
+
|
55
|
+
* Added support for TSV output.
|
56
|
+
|
3
57
|
## 0.5.8 - 2017-11-09
|
4
58
|
|
5
59
|
### Improvements
|
data/groonga-client.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- mode: ruby -*-
|
2
2
|
#
|
3
3
|
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
4
|
-
# Copyright (C) 2014-
|
4
|
+
# Copyright (C) 2014-2020 Sutou Kouhei <kou@clear-code.com>
|
5
5
|
#
|
6
6
|
# This library is free software; you can redistribute it and/or
|
7
7
|
# modify it under the terms of the GNU Lesser General Public
|
@@ -48,7 +48,7 @@ Gem::Specification.new do |spec|
|
|
48
48
|
end
|
49
49
|
|
50
50
|
spec.add_runtime_dependency("gqtp", ">= 1.0.4")
|
51
|
-
spec.add_runtime_dependency("groonga-command", ">= 1.
|
51
|
+
spec.add_runtime_dependency("groonga-command", ">= 1.4.7")
|
52
52
|
spec.add_runtime_dependency("groonga-command-parser", ">= 1.1.0")
|
53
53
|
spec.add_runtime_dependency("hashie")
|
54
54
|
|
data/lib/groonga/client.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2017 Kouhei Sutou <kou@clear-code.com>
|
3
3
|
#
|
4
4
|
# This library is free software; you can redistribute it and/or
|
5
5
|
# modify it under the terms of the GNU Lesser General Public
|
@@ -23,6 +23,7 @@ require "groonga/client/command"
|
|
23
23
|
require "groonga/client/empty-request"
|
24
24
|
require "groonga/client/protocol/gqtp"
|
25
25
|
require "groonga/client/protocol/http"
|
26
|
+
require "groonga/client/protocol/file"
|
26
27
|
require "groonga/client/request"
|
27
28
|
|
28
29
|
module Groonga
|
@@ -100,9 +101,11 @@ module Groonga
|
|
100
101
|
@connection = Groonga::Client::Protocol::GQTP.new(url, options)
|
101
102
|
when "http", "https"
|
102
103
|
@connection = Groonga::Client::Protocol::HTTP.new(url, options)
|
104
|
+
when "file"
|
105
|
+
@connection = Groonga::Client::Protocol::File.new(url, options)
|
103
106
|
else
|
104
107
|
message = "unsupported scheme: <#{url.scheme}>: "
|
105
|
-
message << "supported: [gqtp, http, https]"
|
108
|
+
message << "supported: [gqtp, http, https, file]"
|
106
109
|
raise ArgumentError, message
|
107
110
|
end
|
108
111
|
end
|
@@ -219,7 +222,7 @@ module Groonga
|
|
219
222
|
scheme = (options.delete(:protocol) || "gqtp").to_s
|
220
223
|
host = options.delete(:host) || options.delete(:address) || "127.0.0.1"
|
221
224
|
port = options.delete(:port) || default_port(scheme)
|
222
|
-
path = options.delete(:path)
|
225
|
+
path = options.delete(:path) || default_path(scheme)
|
223
226
|
user = options.delete(:user)
|
224
227
|
password = options.delete(:password)
|
225
228
|
if user and password
|
@@ -260,6 +263,15 @@ module Groonga
|
|
260
263
|
end
|
261
264
|
end
|
262
265
|
|
266
|
+
def default_path(scheme)
|
267
|
+
case scheme
|
268
|
+
when "http", "https"
|
269
|
+
"/d/"
|
270
|
+
else
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
263
275
|
def groonga_command_name?(name)
|
264
276
|
/\A[a-zA-Z][a-zA-Z\d_]+\z/ === name.to_s
|
265
277
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2015-
|
1
|
+
# Copyright (C) 2015-2020 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
|
@@ -14,9 +14,16 @@
|
|
14
14
|
# License along with this library; if not, write to the Free Software
|
15
15
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
16
16
|
|
17
|
+
require "fileutils"
|
17
18
|
require "json"
|
19
|
+
require "pathname"
|
18
20
|
require "securerandom"
|
19
21
|
|
22
|
+
begin
|
23
|
+
require "arrow"
|
24
|
+
rescue LoadError
|
25
|
+
end
|
26
|
+
|
20
27
|
require "groonga/command/parser"
|
21
28
|
|
22
29
|
require "groonga/client"
|
@@ -28,10 +35,19 @@ module Groonga
|
|
28
35
|
class GroongaClient
|
29
36
|
def initialize
|
30
37
|
@chunk = false
|
38
|
+
@load_input_type = "json"
|
39
|
+
@available_load_input_types = ["json"]
|
40
|
+
if Object.const_defined?(:Arrow)
|
41
|
+
@available_load_input_types << "apache-arrow"
|
42
|
+
end
|
43
|
+
@load_lock_table = false
|
31
44
|
|
32
45
|
@runner_options = {
|
33
46
|
:split_load_chunk_size => 10000,
|
34
47
|
:generate_request_id => false,
|
48
|
+
:target_commands => [],
|
49
|
+
:target_tables => [],
|
50
|
+
:target_columns => [],
|
35
51
|
}
|
36
52
|
end
|
37
53
|
|
@@ -41,12 +57,25 @@ module Groonga
|
|
41
57
|
parse_command_line(option_parser)
|
42
58
|
end
|
43
59
|
|
44
|
-
parser.open_client(:chunk => @chunk
|
60
|
+
parser.open_client(:chunk => @chunk,
|
61
|
+
:load_input_type => @load_input_type,
|
62
|
+
:load_lock_table => @load_lock_table) do |client|
|
45
63
|
runner = Runner.new(client, @runner_options)
|
46
64
|
|
47
65
|
if command_file_paths.empty?
|
48
|
-
$stdin.
|
49
|
-
|
66
|
+
if $stdin.tty? and $stdout.tty?
|
67
|
+
begin
|
68
|
+
require "readline"
|
69
|
+
rescue LoadError
|
70
|
+
repl = BareREPL.new(runner)
|
71
|
+
else
|
72
|
+
repl = ReadlineREPL.new(runner)
|
73
|
+
end
|
74
|
+
repl.run
|
75
|
+
else
|
76
|
+
$stdin.each_line do |line|
|
77
|
+
runner << line
|
78
|
+
end
|
50
79
|
end
|
51
80
|
else
|
52
81
|
command_file_paths.each do |command_file_path|
|
@@ -83,6 +112,20 @@ module Groonga
|
|
83
112
|
@runner_options[:split_load_chunk_size] = size
|
84
113
|
end
|
85
114
|
|
115
|
+
parser.on("--load-input-type=TYPE",
|
116
|
+
@available_load_input_types,
|
117
|
+
"Use TYPE as input type for load.",
|
118
|
+
"[#{@available_load_input_types.join(", ")}]",
|
119
|
+
"(#{@load_input_types})") do |type|
|
120
|
+
@load_input_type = type
|
121
|
+
end
|
122
|
+
|
123
|
+
parser.on("--[no-]load-lock-table",
|
124
|
+
"Use lock_table=yes for load.",
|
125
|
+
"(#{@load_lock_table})") do |boolean|
|
126
|
+
@load_lock_table = boolean
|
127
|
+
end
|
128
|
+
|
86
129
|
parser.on("--[no-]generate-request-id",
|
87
130
|
"Add auto generated request ID to all commands.",
|
88
131
|
"(#{@runner_options[:generate_request_id]})") do |boolean|
|
@@ -95,6 +138,39 @@ module Groonga
|
|
95
138
|
"(#{@chunk})") do |boolean|
|
96
139
|
@chunk = boolean
|
97
140
|
end
|
141
|
+
|
142
|
+
parser.on("--target-command=COMMAND",
|
143
|
+
"Add COMMAND as target commands",
|
144
|
+
"You can specify multiple times",
|
145
|
+
"If COMMAND is /.../,",
|
146
|
+
"it's treated as a regular expression") do |command|
|
147
|
+
add_target(@runner_options[:target_commands], command)
|
148
|
+
end
|
149
|
+
|
150
|
+
parser.on("--target-table=TABLE",
|
151
|
+
"Add TABLE as target tables",
|
152
|
+
"You can specify multiple times",
|
153
|
+
"If TABLE is /.../,",
|
154
|
+
"it's treated as a regular expression") do |table|
|
155
|
+
add_target(@runner_options[:target_tables], table)
|
156
|
+
end
|
157
|
+
|
158
|
+
parser.on("--target-column=COLUMN",
|
159
|
+
"Add COLUMN as target columns",
|
160
|
+
"You can specify multiple times",
|
161
|
+
"If COLUMN is /.../,",
|
162
|
+
"it's treated as a regular expression") do |column|
|
163
|
+
add_target(@runner_options[:target_columns], column)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def add_target(targets, target)
|
168
|
+
if /\A\\(.+?)\\(i)?\z/ =~ target
|
169
|
+
pattern = Regexp.new($1, $2 == "i")
|
170
|
+
targets << pattern
|
171
|
+
else
|
172
|
+
targets << target
|
173
|
+
end
|
98
174
|
end
|
99
175
|
|
100
176
|
class Runner
|
@@ -102,6 +178,9 @@ module Groonga
|
|
102
178
|
@client = client
|
103
179
|
@split_load_chunk_size = options[:split_load_chunk_size] || 10000
|
104
180
|
@generate_request_id = options[:generate_request_id]
|
181
|
+
@target_commands = options[:target_commands]
|
182
|
+
@target_tables = options[:target_tables]
|
183
|
+
@target_columns = options[:target_columns]
|
105
184
|
@load_values = []
|
106
185
|
@parser = create_command_parser
|
107
186
|
end
|
@@ -114,6 +193,17 @@ module Groonga
|
|
114
193
|
@parser.finish
|
115
194
|
end
|
116
195
|
|
196
|
+
def repl
|
197
|
+
begin
|
198
|
+
require "readline"
|
199
|
+
rescue LoadError
|
200
|
+
repl = BareREPL.new(self)
|
201
|
+
else
|
202
|
+
repl = ReadlineREPL.new(self)
|
203
|
+
repl_readline
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
117
207
|
private
|
118
208
|
def create_command_parser
|
119
209
|
parser = Groonga::Command::Parser.new
|
@@ -164,6 +254,12 @@ module Groonga
|
|
164
254
|
end
|
165
255
|
|
166
256
|
def run_command(command)
|
257
|
+
return unless target_command?(command)
|
258
|
+
return unless target_table?(command)
|
259
|
+
return unless target_column?(command)
|
260
|
+
|
261
|
+
command = Marshal.load(Marshal.dump(command))
|
262
|
+
apply_target_columns(command)
|
167
263
|
command[:request_id] ||= SecureRandom.uuid if @generate_request_id
|
168
264
|
response = @client.execute(command)
|
169
265
|
case command.output_type
|
@@ -175,6 +271,184 @@ module Groonga
|
|
175
271
|
puts(response.body)
|
176
272
|
end
|
177
273
|
end
|
274
|
+
|
275
|
+
def target_command?(command)
|
276
|
+
return true if @target_commands.empty?
|
277
|
+
|
278
|
+
@target_commands.any? do |name|
|
279
|
+
name === command.command_name
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def target_table?(command)
|
284
|
+
return true if @target_tables.empty?
|
285
|
+
|
286
|
+
target = nil
|
287
|
+
case command.command_name
|
288
|
+
when "load", "column_create", "select"
|
289
|
+
target = command.table
|
290
|
+
when "table_create", "table_remove"
|
291
|
+
target = command.name
|
292
|
+
end
|
293
|
+
return true if target.nil?
|
294
|
+
|
295
|
+
@target_tables.any? do |name|
|
296
|
+
name === target
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def target_column?(command)
|
301
|
+
return true if @target_columns.empty?
|
302
|
+
|
303
|
+
target = nil
|
304
|
+
case command.command_name
|
305
|
+
when "column_create"
|
306
|
+
target = command.name
|
307
|
+
end
|
308
|
+
return true if target.nil?
|
309
|
+
|
310
|
+
@target_columns.any? do |name|
|
311
|
+
name === target
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def apply_target_columns(command)
|
316
|
+
return if @target_columns.empty?
|
317
|
+
|
318
|
+
values = command[:values]
|
319
|
+
return if values.nil?
|
320
|
+
|
321
|
+
command = command.dup
|
322
|
+
|
323
|
+
values = JSON.parse(values)
|
324
|
+
columns = command[:columns]
|
325
|
+
if columns
|
326
|
+
columns = columns.split(/\s*,\s*/)
|
327
|
+
target_indexes = []
|
328
|
+
new_columns = []
|
329
|
+
columns.each_with_index do |column, i|
|
330
|
+
if load_target_column?(column)
|
331
|
+
target_indexes << i
|
332
|
+
new_columns << column
|
333
|
+
end
|
334
|
+
end
|
335
|
+
command[:columns] = new_columns.join(",")
|
336
|
+
new_values = values.collect do |value|
|
337
|
+
target_indexes.collect do |i|
|
338
|
+
value[i]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
command[:values] = JSON.generate(new_values)
|
342
|
+
else
|
343
|
+
new_values = values.collect do |value|
|
344
|
+
new_value = {}
|
345
|
+
value.each do |key, value|
|
346
|
+
if load_target_column?(key)
|
347
|
+
new_value[key] = value
|
348
|
+
end
|
349
|
+
end
|
350
|
+
new_value
|
351
|
+
end
|
352
|
+
command[:values] = JSON.generate(new_values)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def load_target_column?(column)
|
357
|
+
column == "_key" or
|
358
|
+
@target_columns.any? {|name| name === column}
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class BareREPL
|
363
|
+
def initialize(runner)
|
364
|
+
@runner = runner
|
365
|
+
end
|
366
|
+
|
367
|
+
def run
|
368
|
+
loop do
|
369
|
+
print("> ")
|
370
|
+
$stdout.flush
|
371
|
+
line = gets
|
372
|
+
break if line.nil?
|
373
|
+
@runner << line
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
class ReadlineREPL
|
379
|
+
def initialize(runner)
|
380
|
+
@runner = runner
|
381
|
+
@history_path = guess_history_path
|
382
|
+
read_history
|
383
|
+
end
|
384
|
+
|
385
|
+
def run
|
386
|
+
loop do
|
387
|
+
line = Readline.readline("> ", true)
|
388
|
+
break if line.nil?
|
389
|
+
add_history(line)
|
390
|
+
@runner << line
|
391
|
+
@runner << "\n"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
private
|
396
|
+
def guess_history_path
|
397
|
+
case RUBY_PLATFORM
|
398
|
+
when /mswin/, /mingw/
|
399
|
+
base_dir = ENV["LOCALAPPDATA"] || "~/AppData"
|
400
|
+
when /darwin/
|
401
|
+
base_dir = "~/Library/Preferences"
|
402
|
+
else
|
403
|
+
base_dir = ENV["XDG_CONFIG_HOME"] || "~/.config"
|
404
|
+
end
|
405
|
+
Pathname(base_dir).expand_path + "groonga-client" + "history.txt"
|
406
|
+
end
|
407
|
+
|
408
|
+
def read_history
|
409
|
+
if @history_path.exist?
|
410
|
+
@history_path.open do |history_file|
|
411
|
+
history_file.each_line do |line|
|
412
|
+
Readline::HISTORY << line.chomp
|
413
|
+
end
|
414
|
+
end
|
415
|
+
@history_timestamp = @history_path.mtime
|
416
|
+
else
|
417
|
+
@history_timestamp = Time.now
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def add_history(entry)
|
422
|
+
updated = history_is_updated?
|
423
|
+
|
424
|
+
if new_history_entry?(entry)
|
425
|
+
FileUtils.mkdir_p(@history_path.dirname)
|
426
|
+
@history_path.open("a") do |history_file|
|
427
|
+
history_file << entry
|
428
|
+
history_file << "\n"
|
429
|
+
end
|
430
|
+
else
|
431
|
+
Readline::HISTORY.pop
|
432
|
+
end
|
433
|
+
|
434
|
+
if updated
|
435
|
+
Readline::HISTORY.clear
|
436
|
+
read_history
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def history_is_updated?
|
441
|
+
@history_path.exist? and
|
442
|
+
@history_path.mtime > @history_timestamp
|
443
|
+
end
|
444
|
+
|
445
|
+
def new_history_entry?(entry)
|
446
|
+
return false if /\A\s*\z/ =~ entry
|
447
|
+
if Readline::HISTORY.size > 1 and Readline::HISTORY[-2] == entry
|
448
|
+
return false
|
449
|
+
end
|
450
|
+
true
|
451
|
+
end
|
178
452
|
end
|
179
453
|
end
|
180
454
|
end
|