groonga-client 0.5.4 → 0.5.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/bin/groonga-client +0 -6
- data/bin/groonga-client-index-check +24 -0
- data/doc/text/news.md +11 -0
- data/groonga-client.gemspec +1 -1
- data/lib/groonga/client/command-line/groonga-client-index-check.rb +190 -0
- data/lib/groonga/client/command-line/groonga-client-index-recreate.rb +23 -137
- data/lib/groonga/client/command-line/groonga-client.rb +11 -73
- data/lib/groonga/client/command-line/parser.rb +111 -0
- data/lib/groonga/client/command-line/runner.rb +122 -0
- data/lib/groonga/client/protocol/http/synchronous.rb +22 -1
- data/lib/groonga/client/response/column-list.rb +10 -0
- data/lib/groonga/client/version.rb +1 -1
- data/test/command-line/helper.rb +87 -0
- data/test/command-line/test-index-check.rb +292 -0
- data/test/command-line/test-index-recreate.rb +4 -63
- data/test/response/test-column-list.rb +54 -0
- metadata +37 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b31699984e9b4cfb9751dc8cf4481031d10a2fa1
|
4
|
+
data.tar.gz: ea990b190ba221e32990e5b2f08118acb3b2ca63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3485123969cc7cdf43b7c1a8afba2ec7b58d5eff387c3506bcdc344bd0a6d4da3ddf5c65dca1018fe58fa94531510c5f47ba4b5e455ac988fbc44fd0b831631e
|
7
|
+
data.tar.gz: fc4ce6ee9206924e5b44d59243fb8a1544e15cf5cf7adcd87105c977443c4aa233e4165a99d3df01d1914841f3178a3c274779172d49449d835eb4fd1fc02dc0
|
data/bin/groonga-client
CHANGED
@@ -17,12 +17,6 @@
|
|
17
17
|
# License along with this library; if not, write to the Free Software
|
18
18
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
19
19
|
|
20
|
-
require "net/http"
|
21
|
-
|
22
|
-
if Net::HTTP.const_defined?(:IDEMPOTENT_METHODS_)
|
23
|
-
Net::HTTP::IDEMPOTENT_METHODS_.clear
|
24
|
-
end
|
25
|
-
|
26
20
|
require "groonga/client/command-line/groonga-client"
|
27
21
|
|
28
22
|
command_line = Groonga::Client::CommandLine::GroongaClient.new
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
#
|
4
|
+
# Copyright (C) 2015-2016 Kouhei Sutou <kou@clear-code.com>
|
5
|
+
# Copyright (C) 2017 Kentaro Hayashi <hayashi@clear-code.com>
|
6
|
+
#
|
7
|
+
# This library is free software; you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU Lesser General Public
|
9
|
+
# License as published by the Free Software Foundation; either
|
10
|
+
# version 2.1 of the License, or (at your option) any later version.
|
11
|
+
#
|
12
|
+
# This library is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15
|
+
# Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public
|
18
|
+
# License along with this library; if not, write to the Free Software
|
19
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
20
|
+
|
21
|
+
require "groonga/client/command-line/groonga-client-index-check"
|
22
|
+
|
23
|
+
command_line = Groonga::Client::CommandLine::GroongaClientIndexCheck.new
|
24
|
+
exit(command_line.run(ARGV))
|
data/doc/text/news.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# NEWS
|
2
2
|
|
3
|
+
## 0.5.5 - 2017-10-31
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* `groonga-client-index-check`: Added a new command that finds
|
8
|
+
indexes which doesn't have source or indexes whose content is
|
9
|
+
broken.
|
10
|
+
|
11
|
+
* Disabled auto retry feature by `net/http` explicitly because `GET`
|
12
|
+
isn't idempotent request in Groonga such as `delete` command.
|
13
|
+
|
3
14
|
## 0.5.4 - 2017-10-27
|
4
15
|
|
5
16
|
### Improvements
|
data/groonga-client.gemspec
CHANGED
@@ -49,7 +49,7 @@ Gem::Specification.new do |spec|
|
|
49
49
|
|
50
50
|
spec.add_runtime_dependency("gqtp", ">= 1.0.4")
|
51
51
|
spec.add_runtime_dependency("groonga-command", ">= 1.2.8")
|
52
|
-
spec.add_runtime_dependency("groonga-command-parser", ">= 1.0
|
52
|
+
spec.add_runtime_dependency("groonga-command-parser", ">= 1.1.0")
|
53
53
|
spec.add_runtime_dependency("hashie")
|
54
54
|
|
55
55
|
spec.add_development_dependency("bundler")
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# Copyright (C) 2015-2017 Kouhei Sutou <kou@clear-code.com>
|
2
|
+
# Copyright (C) 2017 Kentaro Hayashi <hayashi@clear-code.com>
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
6
|
+
# License as published by the Free Software Foundation; either
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
require "groonga/client"
|
19
|
+
require "groonga/client/command-line/parser"
|
20
|
+
require "groonga/client/command-line/runner"
|
21
|
+
|
22
|
+
module Groonga
|
23
|
+
class Client
|
24
|
+
module CommandLine
|
25
|
+
class GroongaClientIndexCheck
|
26
|
+
def initialize
|
27
|
+
@available_methods = [:source, :content]
|
28
|
+
@methods = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(arguments)
|
32
|
+
parser = Parser.new
|
33
|
+
target_names = parser.parse(arguments) do |option_parser|
|
34
|
+
parse_command_line(option_parser)
|
35
|
+
end
|
36
|
+
|
37
|
+
if @methods.empty?
|
38
|
+
@methods = @available_methods
|
39
|
+
end
|
40
|
+
|
41
|
+
parser.open_client do |client|
|
42
|
+
checker = Checker.new(client, @methods, target_names)
|
43
|
+
checker.run
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def parse_command_line(parser)
|
49
|
+
parser.banner += " [LEXICON1.INDEX1 LEXICON2.INDEX2 LEXICON3 ...]"
|
50
|
+
|
51
|
+
parser.separator("")
|
52
|
+
parser.separator("If no indexes are specified, " +
|
53
|
+
"all indexes are checked.")
|
54
|
+
|
55
|
+
parser.separator("")
|
56
|
+
parser.separator("Method:")
|
57
|
+
|
58
|
+
parser.on("--method=METHOD", @available_methods,
|
59
|
+
"Specify a method how to check indexes.",
|
60
|
+
"You can specify this option multiple times",
|
61
|
+
"to use multiple methods in one execution.",
|
62
|
+
"All methods are used by default.",
|
63
|
+
"Available methods:",
|
64
|
+
" source: Find indexes that don't have source.",
|
65
|
+
" content: Find indexes whose content is broken.",
|
66
|
+
"(#{@available_methods.join(", ")})") do |method|
|
67
|
+
@methods << method
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Checker < Runner
|
72
|
+
def initialize(client, methods, target_names)
|
73
|
+
super(client)
|
74
|
+
@methods = methods
|
75
|
+
@target_names = target_names
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def run_internal
|
80
|
+
succeeded = true
|
81
|
+
each_target_index_column do |index_column|
|
82
|
+
@methods.each do |method|
|
83
|
+
unless __send__("check_#{method}", index_column)
|
84
|
+
succeeded = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
succeeded
|
89
|
+
end
|
90
|
+
|
91
|
+
def each_target_index_column
|
92
|
+
table_list.each do |table|
|
93
|
+
next unless target_table?(table)
|
94
|
+
column_list(table.name).each do |column|
|
95
|
+
next unless column.index?
|
96
|
+
next unless target_column?(column)
|
97
|
+
yield(column)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def target_table?(table)
|
103
|
+
return true if @target_names.empty?
|
104
|
+
@target_names.any? do |name|
|
105
|
+
if name.include?(".")
|
106
|
+
index_table_name = name.split(".").first
|
107
|
+
index_table_name == table.name
|
108
|
+
else
|
109
|
+
name == table.name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def target_column?(column)
|
115
|
+
return true if @target_names.empty?
|
116
|
+
@target_names.any? do |name|
|
117
|
+
if name.include?(".")
|
118
|
+
name == column.full_name
|
119
|
+
else
|
120
|
+
name == column.domain
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def check_source(column)
|
126
|
+
return true unless column.source.empty?
|
127
|
+
$stderr.puts("Source is missing: <#{column.full_name}>")
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
def valid_token?(source_table_name,
|
132
|
+
full_index_column_name1,
|
133
|
+
full_index_column_name2,
|
134
|
+
token)
|
135
|
+
case token
|
136
|
+
when String
|
137
|
+
value = Groonga::Client::ScriptSyntax.format_string(token)
|
138
|
+
else
|
139
|
+
value = token
|
140
|
+
end
|
141
|
+
response1 = select(source_table_name,
|
142
|
+
:filter => "#{full_index_column_name1} @ #{value}",
|
143
|
+
:output_columns => "_id",
|
144
|
+
:limit => "-1",
|
145
|
+
:sort_keys => "_id")
|
146
|
+
response2 = select(source_table_name,
|
147
|
+
:filter => "#{full_index_column_name2} @ #{value}",
|
148
|
+
:output_columns => "_id",
|
149
|
+
:limit => "-1",
|
150
|
+
:sort_keys => "_id")
|
151
|
+
response1.records == response2.records
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_content(index_column)
|
155
|
+
return if index_column.source.empty?
|
156
|
+
|
157
|
+
lexicon_name = index_column.domain
|
158
|
+
index_column_name = index_column.name
|
159
|
+
suffix = Time.now.strftime("%Y%m%d%H%M%S_%N")
|
160
|
+
new_index_column_name = "#{index_column_name}_#{suffix}"
|
161
|
+
full_index_column_name = index_column.full_name
|
162
|
+
full_new_index_column_name = "#{full_index_column_name}_#{suffix}"
|
163
|
+
source_table = index_column.range
|
164
|
+
column_create_similar(lexicon_name,
|
165
|
+
new_index_column_name,
|
166
|
+
index_column_name)
|
167
|
+
begin
|
168
|
+
response = select(lexicon_name,
|
169
|
+
:limit => "-1",
|
170
|
+
:output_columns => "_key")
|
171
|
+
response.records.each do |record|
|
172
|
+
token = record["_key"]
|
173
|
+
unless valid_token?(source_table,
|
174
|
+
full_index_column_name,
|
175
|
+
full_new_index_column_name,
|
176
|
+
token)
|
177
|
+
$stderr.puts("Broken: #{index_column.full_name}: <#{token}>")
|
178
|
+
return false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
true
|
182
|
+
ensure
|
183
|
+
column_remove(lexicon_name, new_index_column_name)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -14,39 +14,31 @@
|
|
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 "optparse"
|
18
17
|
require "json"
|
19
18
|
|
20
19
|
require "groonga/client"
|
20
|
+
require "groonga/client/command-line/parser"
|
21
|
+
require "groonga/client/command-line/runner"
|
21
22
|
|
22
23
|
module Groonga
|
23
24
|
class Client
|
24
25
|
module CommandLine
|
25
26
|
class GroongaClientIndexRecreate
|
26
27
|
def initialize
|
27
|
-
@url = nil
|
28
|
-
@protocol = :http
|
29
|
-
@host = "localhost"
|
30
|
-
@port = nil
|
31
|
-
|
32
|
-
@read_timeout = -1
|
33
|
-
|
34
28
|
@interval = :day
|
35
29
|
|
36
30
|
@n_workers = 0
|
37
31
|
end
|
38
32
|
|
39
|
-
def run(
|
40
|
-
|
33
|
+
def run(arguments)
|
34
|
+
parser = Parser.new(:read_timeout => -1)
|
35
|
+
indexes = parser.parse(arguments) do |option_parser|
|
36
|
+
parse_command_line(option_parser)
|
37
|
+
end
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
:port => @port,
|
46
|
-
:read_timeout => @read_timeout,
|
47
|
-
:backend => :synchronous) do |client|
|
48
|
-
runner = Runner.new(client, @interval, target_indexes)
|
49
|
-
runner.run do
|
39
|
+
parser.open_client do |client|
|
40
|
+
recreator = Recreator.new(client, @interval, indexes)
|
41
|
+
recreator.run do
|
50
42
|
@n_workers.times do
|
51
43
|
client.database_unmap
|
52
44
|
end
|
@@ -55,48 +47,9 @@ module Groonga
|
|
55
47
|
end
|
56
48
|
|
57
49
|
private
|
58
|
-
def parse_command_line(
|
59
|
-
parser = OptionParser.new
|
60
|
-
parser.version = VERSION
|
50
|
+
def parse_command_line(parser)
|
61
51
|
parser.banner += " LEXICON1.INDEX1 LEXICON2.INDEX2 ..."
|
62
52
|
|
63
|
-
parser.separator("")
|
64
|
-
parser.separator("Connection:")
|
65
|
-
|
66
|
-
parser.on("--url=URL",
|
67
|
-
"URL to connect to Groonga server.",
|
68
|
-
"If this option is specified,",
|
69
|
-
"--protocol, --host and --port are ignored.") do |url|
|
70
|
-
@url = url
|
71
|
-
end
|
72
|
-
|
73
|
-
available_protocols = [:http, :gqtp]
|
74
|
-
parser.on("--protocol=PROTOCOL", [:http, :gqtp],
|
75
|
-
"Protocol to connect to Groonga server.",
|
76
|
-
"[#{available_protocols.join(", ")}]",
|
77
|
-
"(#{@protocol})") do |protocol|
|
78
|
-
@protocol = protocol
|
79
|
-
end
|
80
|
-
|
81
|
-
parser.on("--host=HOST",
|
82
|
-
"Groonga server to be connected.",
|
83
|
-
"(#{@host})") do |host|
|
84
|
-
@host = host
|
85
|
-
end
|
86
|
-
|
87
|
-
parser.on("--port=PORT", Integer,
|
88
|
-
"Port number of Groonga server to be connected.",
|
89
|
-
"(auto)") do |port|
|
90
|
-
@port = port
|
91
|
-
end
|
92
|
-
|
93
|
-
parser.on("--read-timeout=TIMEOUT", Integer,
|
94
|
-
"Timeout on reading response from Groonga server.",
|
95
|
-
"You can disable timeout by specifying -1.",
|
96
|
-
"(#{@read_timeout})") do |timeout|
|
97
|
-
@read_timeout = timeout
|
98
|
-
end
|
99
|
-
|
100
53
|
parser.separator("")
|
101
54
|
parser.separator("Configuration:")
|
102
55
|
|
@@ -117,91 +70,25 @@ module Groonga
|
|
117
70
|
"(#{@n_workers})") do |n|
|
118
71
|
@n_workers = n
|
119
72
|
end
|
120
|
-
|
121
|
-
target_indexes = parser.parse(argv)
|
122
|
-
|
123
|
-
@port ||= default_port(@protocol)
|
124
|
-
|
125
|
-
target_indexes
|
126
|
-
end
|
127
|
-
|
128
|
-
def default_port(protocol)
|
129
|
-
case protocol
|
130
|
-
when :http
|
131
|
-
10041
|
132
|
-
when :gqtp
|
133
|
-
10043
|
134
|
-
end
|
135
73
|
end
|
136
74
|
|
137
|
-
class Runner
|
75
|
+
class Recreator < Runner
|
138
76
|
def initialize(client, interval, target_indexes)
|
139
|
-
|
77
|
+
super(client)
|
140
78
|
@interval = interval
|
141
79
|
@target_indexes = target_indexes
|
142
80
|
@now = Time.now
|
143
81
|
end
|
144
82
|
|
145
|
-
def run
|
146
|
-
catch do |tag|
|
147
|
-
@abort_tag = tag
|
148
|
-
alias_column = ensure_alias_column
|
149
|
-
@target_indexes.each do |index|
|
150
|
-
current_index = recreate_index(index, alias_column)
|
151
|
-
remove_old_indexes(index, current_index)
|
152
|
-
end
|
153
|
-
yield if block_given?
|
154
|
-
true
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
83
|
private
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def execute_command(name, arguments={})
|
165
|
-
response = @client.execute(name, arguments)
|
166
|
-
unless response.success?
|
167
|
-
abort_run("Failed to run #{name}: #{response.inspect}")
|
84
|
+
def run_internal
|
85
|
+
alias_column = ensure_alias_column
|
86
|
+
@target_indexes.each do |index|
|
87
|
+
current_index = recreate_index(index, alias_column)
|
88
|
+
remove_old_indexes(index, current_index)
|
168
89
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
def config_get(key)
|
173
|
-
execute_command(:config_get, :key => key).body
|
174
|
-
end
|
175
|
-
|
176
|
-
def config_set(key, value)
|
177
|
-
execute_command(:config_set, :key => key, :value => value).body
|
178
|
-
end
|
179
|
-
|
180
|
-
def object_exist?(name)
|
181
|
-
execute_command(:object_exist, :name => name).body
|
182
|
-
end
|
183
|
-
|
184
|
-
def column_rename(table, name, new_name)
|
185
|
-
execute_command(:column_rename,
|
186
|
-
:table => table,
|
187
|
-
:name => name,
|
188
|
-
:new_name => new_name).body
|
189
|
-
end
|
190
|
-
|
191
|
-
def column_list(table)
|
192
|
-
execute_command(:column_list, :table => table)
|
193
|
-
end
|
194
|
-
|
195
|
-
def column_remove(table, column)
|
196
|
-
execute_command(:column_remove,
|
197
|
-
:table => table,
|
198
|
-
:name => column)
|
199
|
-
end
|
200
|
-
|
201
|
-
def column_create_similar(table, column_name, base_column_name)
|
202
|
-
info = execute_command(:schema)["#{table}.#{base_column_name}"]
|
203
|
-
arguments = info.command.arguments.merge("name" => column_name)
|
204
|
-
execute_command(:column_create, arguments).body
|
90
|
+
yield if block_given?
|
91
|
+
true
|
205
92
|
end
|
206
93
|
|
207
94
|
def set_alias(alias_column, alias_name, real_name)
|
@@ -229,10 +116,9 @@ module Groonga
|
|
229
116
|
def resolve_alias(alias_column, key)
|
230
117
|
table, column = alias_column.split(".", 2)
|
231
118
|
filter = "_key == #{ScriptSyntax.format_string(key)}"
|
232
|
-
response =
|
233
|
-
|
234
|
-
|
235
|
-
:output_columns => column)
|
119
|
+
response = select(table,
|
120
|
+
:filter => filter,
|
121
|
+
:output_columns => column)
|
236
122
|
return nil if response.n_hits.zero?
|
237
123
|
response.records.first[column]
|
238
124
|
end
|