rsql 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|