groonga-client 0.5.8 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4513c924b1dc4c7ca3b26afc382163620d09d26c
4
- data.tar.gz: dd01a037ba7ff770cf04b6d3118d9847061e0fe4
2
+ SHA256:
3
+ metadata.gz: 29a5323d61e22fd67047fc6adbf076dfca390ef9fcde76e653d1d52ccff00c55
4
+ data.tar.gz: bcff102d164f18996b61e45e979d63ed4cf812ed1747fe7d454c349eafddbde8
5
5
  SHA512:
6
- metadata.gz: a71e2bd65572c69a35956b928c533754ae6411e5788bc5a412e939eeb7435d765b341ac4f623a8ca30778c8ed7ccf356489007628fafdddb8e94c3716ad2119f
7
- data.tar.gz: 698187d4515e5d45bd1b4572f046ca7e30c915f99b35b676a6027c7c107549b5a52c69b5629612ced8a9d2597f29ea0402536d2574529ff82e025f972f78bebd
6
+ metadata.gz: f3bff560db004312b66a7f38bcf07e3a4a28b5078d6fabc413e0ee58b816dee31bd022539baab88d2adfbd57fc85ad27e43c339aa282b602e98efd67059c9595
7
+ data.tar.gz: e943e75c8985175fb96aeeccbe8189c70c6709d4cb4d6635f547b859e27afd2b80abd6ebe6a5c7b108ceeca1be13e1a2c4f1c12126472cd09d489551a0d41cfe
@@ -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
@@ -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-2016 Kouhei Sutou <kou@clear-code.com>
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.2.8")
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
 
@@ -1,5 +1,5 @@
1
1
  # Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
2
- # Copyright (C) 2013-2016 Kouhei Sutou <kou@clear-code.com>
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-2017 Kouhei Sutou <kou@clear-code.com>
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) do |client|
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.each_line do |line|
49
- runner << line
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