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 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