rsql 0.1.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.
- data/LICENSE +19 -0
- data/README.txt +119 -0
- data/TODO +30 -0
- data/bin/rsql +357 -0
- data/lib/rsql/commands.rb +219 -0
- data/lib/rsql/eval_context.rb +414 -0
- data/lib/rsql/mysql.rb +1127 -0
- data/lib/rsql/mysql_results.rb +320 -0
- data/lib/rsql.rb +8 -0
- metadata +94 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by brad+rsql@gigglewax.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
= rsql
|
2
|
+
|
3
|
+
https://github.com/bradrf/rsql
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
This is an application to make working with a SQL command line more
|
8
|
+
convenient by allowing interaction with Ruby in addition to embedding
|
9
|
+
the common operation of using a SSH connection to an intermediary host
|
10
|
+
for access to the SQL server.
|
11
|
+
|
12
|
+
== SYNOPSIS
|
13
|
+
|
14
|
+
See rsql -help for usage.
|
15
|
+
|
16
|
+
Aside from the standard MySQL command syntax, the following
|
17
|
+
functionality allows for a little more expressive processing.
|
18
|
+
|
19
|
+
Multiple commands can be issued in one set by separation with
|
20
|
+
semicolons.
|
21
|
+
|
22
|
+
Generating SQL
|
23
|
+
--------------
|
24
|
+
|
25
|
+
Ruby code may be called to generate the SQL that is to be executed.
|
26
|
+
This is done by starting any command string with a period. If the
|
27
|
+
final result of evaluating the command string is another string, it is
|
28
|
+
executed as SQL. Any semicolons meant to be processed by Ruby must be
|
29
|
+
escaped. Example:
|
30
|
+
|
31
|
+
rsql> . puts 'hello world!' \\; 'select * from Account'
|
32
|
+
|
33
|
+
Utilizing Canned Methods
|
34
|
+
------------------------
|
35
|
+
|
36
|
+
Commands can be stored in the .rsqlrc file in your HOME directory to
|
37
|
+
expose methods that may be invoked to generate SQL with variable
|
38
|
+
interpolation. Use of the 'register' helper is recommended for this
|
39
|
+
approach. These can then be called in the same way as above. Example:
|
40
|
+
|
41
|
+
In the .rsqlrc file...
|
42
|
+
|
43
|
+
register :users_by_email, :email %q{
|
44
|
+
SELECT * FROM Users WHERE email = '\#\{email\}'
|
45
|
+
}
|
46
|
+
|
47
|
+
...then from the prompt:
|
48
|
+
|
49
|
+
rsql> . users_by_email 'brad@gigglewax.com'
|
50
|
+
|
51
|
+
If a block is provided to the registration, it will be called as a
|
52
|
+
method. Example:
|
53
|
+
|
54
|
+
In the .sqlrc file...
|
55
|
+
|
56
|
+
register :dumby, :hello do |*args|
|
57
|
+
p args
|
58
|
+
end
|
59
|
+
|
60
|
+
rsql> . dumby :world
|
61
|
+
|
62
|
+
All registered methods can be listed using the built-in 'list'
|
63
|
+
command.
|
64
|
+
|
65
|
+
Changes to a sourced file can be reloaded using the built-in 'reload'
|
66
|
+
command.
|
67
|
+
|
68
|
+
Processing Column Data
|
69
|
+
----------------------
|
70
|
+
|
71
|
+
Ruby can be called to process any data on a per-column basis before a
|
72
|
+
displayer is used to render the output. In this way, one can write
|
73
|
+
Ruby to act like MySQL functions on all the data for a given column,
|
74
|
+
converting it into a more readable value. A bang indicator (exlamation
|
75
|
+
point: !) is used to demarcate a mapping of column names to Ruby
|
76
|
+
methods that should be invoked to processes content. Example:
|
77
|
+
|
78
|
+
rsql> select IpAddress from Devices ; ! IpAddress => bin_to_str
|
79
|
+
|
80
|
+
This will call 'bin_to_str' for each 'IpAddress' returned from the
|
81
|
+
query. Mulitple mappings are separated by a comma. These mappings can
|
82
|
+
also be utilized in a canned method. Example:
|
83
|
+
|
84
|
+
register :all_ips, 'select IpAddress from Devices', 'IpAddress' => :bin_to_str
|
85
|
+
|
86
|
+
Redirection
|
87
|
+
-----------
|
88
|
+
|
89
|
+
Output from one or more queries may be post-processed dynamically. If
|
90
|
+
any set of commands is follwed by a greater-than symbol, all results
|
91
|
+
will be stored in a global $results array (with field information
|
92
|
+
stored in $fields) and the final Ruby code will be evaluated with
|
93
|
+
access to them. Any result of the evaluation that is a string is then
|
94
|
+
executed as SQL. Example:
|
95
|
+
|
96
|
+
rsql> select * from Account; select * from Users; > $results.each {|r| p r}
|
97
|
+
|
98
|
+
== LICENSE
|
99
|
+
|
100
|
+
Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
101
|
+
|
102
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
103
|
+
a copy of this software and associated documentation files (the
|
104
|
+
"Software"), to deal in the Software without restriction, including
|
105
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
106
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
107
|
+
permit persons to whom the Software is furnished to do so, subject to
|
108
|
+
the following conditions:
|
109
|
+
|
110
|
+
The above copyright notice and this permission notice shall be
|
111
|
+
included in all copies or substantial portions of the Software.
|
112
|
+
|
113
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
114
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
115
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
116
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
117
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
118
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
119
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/TODO
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
* Support separation of semicolon content from registered sql.
|
2
|
+
|
3
|
+
* Fix multiline dump to be columnar sensitive (i.e. all but the first
|
4
|
+
line will need a prefix of previous column length whitespace.
|
5
|
+
|
6
|
+
* Add option to allow registration to include an automatic displayer.
|
7
|
+
|
8
|
+
* Add ability to save passwords in local OS password storage.
|
9
|
+
|
10
|
+
* Add ability to highlight lines (e.g. to indicate warning lines about
|
11
|
+
some data like LastHeartbeatTime not recent).
|
12
|
+
|
13
|
+
* Add way to prevent this and just allow the raw string to be printed:
|
14
|
+
elsif HEX_RANGE.include?(field.type) && val =~ /[^[:print:]\s]/
|
15
|
+
val = @@eval_context.to_hexstr(val)
|
16
|
+
end
|
17
|
+
|
18
|
+
* Fix overlap of functionality between getting input in rsql and
|
19
|
+
parsing input in commands.rb (e.g. they both duplicate the concepts
|
20
|
+
of looking for exit/quit as well as what the end character(s) should
|
21
|
+
be)
|
22
|
+
|
23
|
+
* Wrap calling of mysql query in a thread to make it interruptable
|
24
|
+
(might need to re-establish a connection if it's stopped but
|
25
|
+
expected a reply...look at the source).
|
26
|
+
|
27
|
+
* Consider using mysql's ping to determine if we need to reconnect.
|
28
|
+
|
29
|
+
* Move last_command logic into mysql_results and remember last five
|
30
|
+
(or so).
|
data/bin/rsql
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
begin
|
24
|
+
# this isn't required unless that's how mysql and net/ssh have
|
25
|
+
# been installed
|
26
|
+
require 'rubygems'
|
27
|
+
rescue LoadError
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'thread'
|
31
|
+
require 'timeout'
|
32
|
+
require 'readline'
|
33
|
+
require 'yaml'
|
34
|
+
require 'net/ssh'
|
35
|
+
|
36
|
+
# allow ourselves to be run from within a source tree
|
37
|
+
if File.symlink?(__FILE__)
|
38
|
+
fn = File.readlink(__FILE__)
|
39
|
+
else
|
40
|
+
fn = __FILE__
|
41
|
+
end
|
42
|
+
libdir = File.expand_path(File.join(File.dirname(fn),'..','lib'))
|
43
|
+
$: << libdir if File.directory?(libdir)
|
44
|
+
|
45
|
+
require 'rsql'
|
46
|
+
include RSQL
|
47
|
+
|
48
|
+
bn = File.basename($0, '.rb')
|
49
|
+
|
50
|
+
eval_context = EvalContext.new
|
51
|
+
|
52
|
+
if i = ARGV.index('-rc')
|
53
|
+
ARGV.delete_at(i)
|
54
|
+
rc_fn = ARGV.delete_at(i)
|
55
|
+
else
|
56
|
+
rc_fn = File.join(ENV['HOME'], ".#{bn}rc")
|
57
|
+
end
|
58
|
+
|
59
|
+
eval_context.load(rc_fn) if File.exists?(rc_fn)
|
60
|
+
|
61
|
+
def get_password(prompt)
|
62
|
+
iswin = nil != (RUBY_PLATFORM =~ /(win|w)32$/)
|
63
|
+
STDOUT.print(prompt)
|
64
|
+
STDOUT.flush
|
65
|
+
`stty -echo` unless iswin
|
66
|
+
password = STDIN.gets
|
67
|
+
password.chomp!
|
68
|
+
ensure
|
69
|
+
`stty echo` unless iswin
|
70
|
+
STDOUT.puts
|
71
|
+
return password
|
72
|
+
end
|
73
|
+
|
74
|
+
# safely separate login credentials while preserving "emtpy" values--
|
75
|
+
# anything of the form [<username>[:<password]@]<host>
|
76
|
+
#
|
77
|
+
def split_login(str)
|
78
|
+
login = []
|
79
|
+
if i = str.rindex(?@)
|
80
|
+
login << str[i+1..-1]
|
81
|
+
if 0 < i
|
82
|
+
str = str[0..i-1]
|
83
|
+
i = str.index(?:)
|
84
|
+
if 0 == i
|
85
|
+
login << '' << str[i+1..-1]
|
86
|
+
elsif i
|
87
|
+
login << str[0..i-1] << str[i+1..-1]
|
88
|
+
else
|
89
|
+
login << str
|
90
|
+
end
|
91
|
+
else
|
92
|
+
login << ''
|
93
|
+
end
|
94
|
+
else
|
95
|
+
login << str
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if ARGV.delete('-help')
|
100
|
+
eval_context.help
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
if ARGV.delete('-version')
|
105
|
+
puts "#{bn} v#{RSQL::VERSION}"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
if i = ARGV.index('-maxrows')
|
110
|
+
ARGV.delete_at(i)
|
111
|
+
MySQLResults.max_rows = ARGV.delete_at(i).to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
if i = ARGV.index('-batch')
|
115
|
+
ARGV.delete_at(i)
|
116
|
+
MySQLResults.field_separator = ARGV.delete_at(i)
|
117
|
+
MySQLResults.field_separator = "\t" if MySQLResults.field_separator == '\t'
|
118
|
+
batch_output = true
|
119
|
+
end
|
120
|
+
|
121
|
+
if i = ARGV.index('-ssh')
|
122
|
+
ARGV.delete_at(i)
|
123
|
+
(ssh_host, ssh_user, ssh_password) = split_login(ARGV.delete_at(i))
|
124
|
+
end
|
125
|
+
|
126
|
+
if i = ARGV.index('-e')
|
127
|
+
ARGV.delete_at(i)
|
128
|
+
batch_input = ''
|
129
|
+
ARGV.delete_if do |arg|
|
130
|
+
arg_i = ARGV.index(arg)
|
131
|
+
if i <= arg_i
|
132
|
+
batch_input << ' ' << arg
|
133
|
+
end
|
134
|
+
end
|
135
|
+
batch_input.strip!
|
136
|
+
end
|
137
|
+
|
138
|
+
if ARGV.size < 1
|
139
|
+
prefix = ' ' << ' ' * bn.size
|
140
|
+
$stderr.puts <<USAGE
|
141
|
+
|
142
|
+
usage: #{bn} [-version] [-help]
|
143
|
+
#{prefix}[-rc <rcfile>] [-maxrows <max>] [-batch <field_separator>]
|
144
|
+
#{prefix}[-ssh [<ssh_user>[:<ssh_password>]@]<ssh_host>]
|
145
|
+
#{prefix}[<mysql_user>[:<mysql_password>]@]<mysql_host>
|
146
|
+
#{prefix}[<database>] [-e <remaining_args_as_input>]
|
147
|
+
|
148
|
+
If -ssh is used, a SSH tunnel is established before trying to
|
149
|
+
connect to the MySQL server.
|
150
|
+
|
151
|
+
Commands may either be passed in non-interactively via the -e option
|
152
|
+
or by piping into the process' standard input (stdin). If using -e,
|
153
|
+
it _must_ be the last option following all other arguments.
|
154
|
+
|
155
|
+
USAGE
|
156
|
+
exit 1
|
157
|
+
end
|
158
|
+
|
159
|
+
(mysql_host, mysql_user, mysql_password) = split_login(ARGV.shift)
|
160
|
+
mysql_password = get_password('mysql password? ') unless mysql_password
|
161
|
+
real_mysql_host = mysql_host
|
162
|
+
|
163
|
+
if ssh_host
|
164
|
+
# randomly pick a tcp port above 1024
|
165
|
+
mysql_port = rand(0xffff-1025) + 1025
|
166
|
+
else
|
167
|
+
mysql_port = 3306
|
168
|
+
end
|
169
|
+
|
170
|
+
db_name = ARGV.shift
|
171
|
+
|
172
|
+
unless $stdin.tty?
|
173
|
+
batch_input = $stdin.read.gsub(/\r?\n/,';')
|
174
|
+
end
|
175
|
+
|
176
|
+
# make sure we remove any duplicates when we add to the history to
|
177
|
+
# keep it clean
|
178
|
+
#
|
179
|
+
def add_to_history(item)
|
180
|
+
found = nil
|
181
|
+
Readline::HISTORY.each_with_index do |h,i|
|
182
|
+
if h == item
|
183
|
+
found = i
|
184
|
+
break
|
185
|
+
end
|
186
|
+
end
|
187
|
+
Readline::HISTORY.delete_at(found) if found
|
188
|
+
Readline::HISTORY.push(item)
|
189
|
+
end
|
190
|
+
|
191
|
+
# try closing but wrapped with a timer so we don't hang forever
|
192
|
+
#
|
193
|
+
def safe_timeout(conn, meth, name)
|
194
|
+
Timeout.timeout(5) { conn.send(meth) }
|
195
|
+
true
|
196
|
+
rescue Timeout::Error
|
197
|
+
$stderr.puts "Timed out waiting to close #{name} connection"
|
198
|
+
false
|
199
|
+
rescue Exception => ex
|
200
|
+
$stderr.puts(ex)
|
201
|
+
false
|
202
|
+
end
|
203
|
+
|
204
|
+
MySQLResults.max_rows ||= batch_output ? 5000 : 1000
|
205
|
+
|
206
|
+
ssh_enabled = false
|
207
|
+
|
208
|
+
if ssh_host
|
209
|
+
|
210
|
+
# might need to open an idle channel here so server doesn't close on
|
211
|
+
# us...or just loop reconnection here in the thread...
|
212
|
+
|
213
|
+
port_opened = false
|
214
|
+
|
215
|
+
puts "SSH #{ssh_user}#{ssh_user ? '@' : ''}#{ssh_host}..." unless batch_input
|
216
|
+
ssh = nil
|
217
|
+
ssh_thread = Thread.new do
|
218
|
+
opts = {:timeout => 15}
|
219
|
+
opts[:password] = ssh_password if ssh_password
|
220
|
+
ssh = Net::SSH.start(ssh_host, ssh_user, opts)
|
221
|
+
ssh_enabled = true
|
222
|
+
ssh.forward.local(mysql_port, mysql_host, 3306)
|
223
|
+
port_opened = true
|
224
|
+
ssh.loop(1) { ssh_enabled }
|
225
|
+
end
|
226
|
+
|
227
|
+
15.times do
|
228
|
+
break if ssh_enabled && port_opened
|
229
|
+
sleep(1)
|
230
|
+
end
|
231
|
+
|
232
|
+
unless ssh_enabled
|
233
|
+
$stderr.puts "failed to connect to #{ssh_host} ssh host in 15 seconds"
|
234
|
+
exit 1
|
235
|
+
end
|
236
|
+
|
237
|
+
unless port_opened
|
238
|
+
$stderr.puts("failed to forward #{mysql_port}:#{mysql_host}:3306 via #{ssh_host} " \
|
239
|
+
"ssh host in 15 seconds")
|
240
|
+
exit 1
|
241
|
+
end
|
242
|
+
|
243
|
+
# now have our mysql connection use our port forward...
|
244
|
+
mysql_host = '127.0.0.1'
|
245
|
+
end
|
246
|
+
|
247
|
+
puts "MySQL #{mysql_user}@#{real_mysql_host}..." unless batch_input
|
248
|
+
begin
|
249
|
+
MySQLResults.conn = Mysql.new(mysql_host, mysql_user, mysql_password, db_name, mysql_port)
|
250
|
+
rescue Mysql::Error => ex
|
251
|
+
if ex.message.include?('Client does not support authentication')
|
252
|
+
$stderr.puts "failed to connect to #{mysql_host} mysql server: unknown credentials?"
|
253
|
+
else
|
254
|
+
$stderr.puts "failed to connect to #{mysql_host} mysql server: #{ex.message}"
|
255
|
+
end
|
256
|
+
exit 1
|
257
|
+
end
|
258
|
+
|
259
|
+
eval_context.call_init_registrations(MySQLResults.conn)
|
260
|
+
|
261
|
+
history_fn = File.join(ENV['HOME'], ".#{bn}_history")
|
262
|
+
if File.exists?(history_fn) && 0 < File.size(history_fn)
|
263
|
+
YAML.load_file(history_fn).each {|i| Readline::HISTORY.push(i)}
|
264
|
+
end
|
265
|
+
|
266
|
+
Readline.completion_proc = eval_context.method(:complete)
|
267
|
+
|
268
|
+
cmd_thread = Thread.new do
|
269
|
+
me = Thread.current
|
270
|
+
me[:shutdown] = false
|
271
|
+
until me[:shutdown] do
|
272
|
+
default_displayer = :display_by_column
|
273
|
+
if batch_input
|
274
|
+
default_displayer = :display_by_batch if batch_output
|
275
|
+
me[:shutdown] = true # only run once
|
276
|
+
input = batch_input
|
277
|
+
else
|
278
|
+
db_name = (MySQLResults.database_name || db_name)
|
279
|
+
puts '',"[#{mysql_user}@#{ssh_host||mysql_host}:#{db_name}]"
|
280
|
+
input = ''
|
281
|
+
prompt = bn + '> '
|
282
|
+
loop do
|
283
|
+
str = Readline.readline(prompt)
|
284
|
+
if str.nil?
|
285
|
+
input = nil if input.empty?
|
286
|
+
break
|
287
|
+
end
|
288
|
+
if str =~ /^\s*(exit|quit)\s*$/
|
289
|
+
me[:shutdown] = true
|
290
|
+
break
|
291
|
+
end
|
292
|
+
input << str
|
293
|
+
break if input =~ /([^\\];|\\G)\s*$/
|
294
|
+
# make sure we separate the lines with some whitespace if
|
295
|
+
# they didn't
|
296
|
+
input << ' ' unless str =~ /\s$/
|
297
|
+
prompt = ''
|
298
|
+
end
|
299
|
+
if input.nil? || me[:shutdown]
|
300
|
+
puts
|
301
|
+
break
|
302
|
+
end
|
303
|
+
input.strip!
|
304
|
+
next if input.empty?
|
305
|
+
end
|
306
|
+
|
307
|
+
add_to_history(input)
|
308
|
+
cmds = Commands.new(input, default_displayer)
|
309
|
+
if cmds.empty?
|
310
|
+
Readline::HISTORY.pop
|
311
|
+
next
|
312
|
+
end
|
313
|
+
|
314
|
+
me[:running] = true
|
315
|
+
break if cmds.run!(eval_context) == :done
|
316
|
+
me[:running] = false
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
Signal.trap('INT') do
|
321
|
+
if cmd_thread[:running] && MySQLResults.conn
|
322
|
+
$stderr.puts 'Interrupting MySQL query...'
|
323
|
+
safe_timeout(MySQLResults.conn, :close, 'MySQL')
|
324
|
+
MySQLResults.conn = nil
|
325
|
+
end
|
326
|
+
$stderr.puts 'Shutting down...'
|
327
|
+
cmd_thread[:shutdown] = true
|
328
|
+
sleep(0.3)
|
329
|
+
cmd_thread.kill
|
330
|
+
end
|
331
|
+
|
332
|
+
begin
|
333
|
+
cmd_thread.join
|
334
|
+
rescue Exception => ex
|
335
|
+
$stderr.puts ex.message, ex.backtrace
|
336
|
+
end
|
337
|
+
|
338
|
+
unless MySQLResults.conn.nil?
|
339
|
+
safe_timeout(MySQLResults.conn, :close, 'MySQL')
|
340
|
+
MySQLResults.conn = nil
|
341
|
+
end
|
342
|
+
|
343
|
+
sleep(0.3)
|
344
|
+
ssh_enabled = false
|
345
|
+
|
346
|
+
if Readline::HISTORY.any?
|
347
|
+
if 100 < Readline::HISTORY.size
|
348
|
+
(Readline::HISTORY.size - 100).times do |i|
|
349
|
+
Readline::HISTORY.delete_at(i)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
File.open(history_fn, 'w') {|f| YAML.dump(Readline::HISTORY.to_a, f)}
|
353
|
+
end
|
354
|
+
|
355
|
+
if ssh_thread
|
356
|
+
safe_timeout(ssh_thread, :join, 'SSH')
|
357
|
+
end
|