rsql 0.2.10 → 0.2.11
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +0 -0
- data/bin/rsql +65 -57
- data/lib/rsql.rb +1 -1
- data/lib/rsql/commands.rb +14 -10
- data/lib/rsql/eval_context.rb +0 -0
- data/lib/rsql/mysql_results.rb +10 -1
- data/test/test_commands.rb +17 -2
- data/test/test_eval_context.rb +3 -0
- data/test/test_mysql_results.rb +7 -0
- metadata +5 -6
- data/extra/mysql-client-5.1.59-1.tgz +0 -0
data/README.rdoc
CHANGED
File without changes
|
data/bin/rsql
CHANGED
@@ -270,6 +270,39 @@ end
|
|
270
270
|
|
271
271
|
MySQLResults.max_rows ||= opts.batch_output ? 5000 : 1000
|
272
272
|
|
273
|
+
# all children processes we need to clean up
|
274
|
+
child_pids = []
|
275
|
+
|
276
|
+
# a file that we can leverage as a poor-man's IPC between the ssh process during
|
277
|
+
# setup and then with the mysql "kill" process
|
278
|
+
ipc_fn = File.join(Dir.tmpdir, "rsql_ipc_#{$$}")
|
279
|
+
|
280
|
+
at_exit do
|
281
|
+
child_pids.each do |pid|
|
282
|
+
begin
|
283
|
+
Process.kill('TERM', pid)
|
284
|
+
killed = false
|
285
|
+
5.times do
|
286
|
+
if Process.waitpid(pid, Process::WNOHANG)
|
287
|
+
killed = true
|
288
|
+
break
|
289
|
+
end
|
290
|
+
sleep 1
|
291
|
+
end
|
292
|
+
Process.kill('KILL', pid) unless killed
|
293
|
+
rescue Errno::ESRCH
|
294
|
+
# do nothing, process is already gone
|
295
|
+
rescue Exception => ex
|
296
|
+
if opts.verbose
|
297
|
+
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
298
|
+
else
|
299
|
+
$stderr.puts(ex.message)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
File.unlink(ipc_fn) if File.exists?(ipc_fn)
|
304
|
+
end
|
305
|
+
|
273
306
|
if opts.ssh_host
|
274
307
|
|
275
308
|
# might need to open an idle channel here so server doesn't close on
|
@@ -287,10 +320,6 @@ if opts.ssh_host
|
|
287
320
|
# calls...so we'll run ssh in the background since its only
|
288
321
|
# purpose is to forward us in for accessing the mysql server
|
289
322
|
|
290
|
-
# we'll use a poor-man's ipc to determine when the ssh process is
|
291
|
-
# ready
|
292
|
-
ipc_fn = File.join(Dir.tmpdir, "rsql_ssh_#{$$}.pid")
|
293
|
-
|
294
323
|
ssh = nil
|
295
324
|
ssh_pid = Process.fork do
|
296
325
|
File.open(ipc_fn,'w'){|f| f.puts('start')}
|
@@ -347,41 +376,15 @@ if opts.ssh_host
|
|
347
376
|
end
|
348
377
|
end
|
349
378
|
|
350
|
-
|
351
|
-
if ssh_pid && 0 <= ssh_pid
|
352
|
-
begin
|
353
|
-
Process.kill('TERM', ssh_pid)
|
354
|
-
killed = false
|
355
|
-
5.times do
|
356
|
-
if Process.waitpid(ssh_pid, Process::WNOHANG)
|
357
|
-
killed = true
|
358
|
-
break
|
359
|
-
end
|
360
|
-
sleep 1
|
361
|
-
end
|
362
|
-
Process.kill('KILL', ssh_pid) unless killed
|
363
|
-
rescue Errno::ESRCH
|
364
|
-
# do nothing, process is already gone
|
365
|
-
rescue Exception => ex
|
366
|
-
if opts.verbose
|
367
|
-
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
368
|
-
else
|
369
|
-
$stderr.puts(ex.message)
|
370
|
-
end
|
371
|
-
end
|
372
|
-
end
|
373
|
-
File.unlink(ipc_fn) if File.exists?(ipc_fn)
|
374
|
-
end
|
379
|
+
child_pids << ssh_pid
|
375
380
|
|
376
381
|
ipc_state = ''
|
377
382
|
15.times do
|
378
383
|
sleep(1)
|
379
|
-
|
384
|
+
ipc_state = IO.read(ipc_fn).strip
|
380
385
|
break if ipc_state == 'ready' || ipc_state == 'fail'
|
381
386
|
end
|
382
387
|
|
383
|
-
File.unlink(ipc_fn)
|
384
|
-
|
385
388
|
unless ipc_state == 'ready'
|
386
389
|
# give the child time to exit
|
387
390
|
sleep(0.5)
|
@@ -402,7 +405,6 @@ mysql_conn = "#{opts.mysql_host}:#{opts.remote_mysql_port || opts.mysql_port}"
|
|
402
405
|
begin
|
403
406
|
MySQLResults.conn = Mysql.new(opts.mysql_host, opts.mysql_user, opts.mysql_password,
|
404
407
|
MySQLResults.database_name, opts.mysql_port)
|
405
|
-
MySQLResults.conn.reconnect = true
|
406
408
|
puts 'connected' unless opts.batch_output
|
407
409
|
rescue Mysql::Error => ex
|
408
410
|
if ex.message.include?('Client does not support authentication')
|
@@ -426,8 +428,6 @@ rescue Exception => ex
|
|
426
428
|
exit 1
|
427
429
|
end
|
428
430
|
|
429
|
-
eval_context.call_init_registrations
|
430
|
-
|
431
431
|
history_fn = File.join(ENV['HOME'], ".#{bn}_history")
|
432
432
|
if File.exists?(history_fn) && 0 < File.size(history_fn)
|
433
433
|
YAML.load_file(history_fn).each {|i| Readline::HISTORY.push(i)}
|
@@ -436,9 +436,17 @@ end
|
|
436
436
|
Readline.completion_proc = eval_context.method(:complete)
|
437
437
|
|
438
438
|
cmd_thread = Thread.new do
|
439
|
+
mysql_tid = nil
|
439
440
|
me = Thread.current
|
440
441
|
me[:shutdown] = false
|
441
442
|
until me[:shutdown] do
|
443
|
+
if mysql_tid != MySQLResults.conn.thread_id
|
444
|
+
# update the thread id for the mysql kill process
|
445
|
+
mysql_tid = MySQLResults.conn.thread_id
|
446
|
+
File.open(ipc_fn,'w'){|f| f.puts(mysql_tid)}
|
447
|
+
eval_context.call_init_registrations
|
448
|
+
end
|
449
|
+
|
442
450
|
default_displayer = :display_by_column
|
443
451
|
if opts.batch_input
|
444
452
|
default_displayer = :display_by_batch if opts.batch_output
|
@@ -466,6 +474,7 @@ cmd_thread = Thread.new do
|
|
466
474
|
prompt = ''
|
467
475
|
end
|
468
476
|
if input.nil? || me[:shutdown]
|
477
|
+
me[:shutdown] = true
|
469
478
|
puts
|
470
479
|
break
|
471
480
|
end
|
@@ -484,33 +493,32 @@ cmd_thread = Thread.new do
|
|
484
493
|
end
|
485
494
|
end
|
486
495
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
Signal.trap('
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
496
|
+
kill_pid = Process.fork do
|
497
|
+
# keep a secondary connection to allow us to kill off a running query in a
|
498
|
+
# subprocess so we don't get bound up by the main process trying to deal
|
499
|
+
# with results
|
500
|
+
kill_conn = Mysql.new(opts.mysql_host, opts.mysql_user, opts.mysql_password, nil, opts.mysql_port)
|
501
|
+
Signal.trap('TERM'){Kernel.exit!}
|
502
|
+
Signal.trap('INT') do
|
503
|
+
mysql_tid = IO.read(ipc_fn).strip.to_i
|
504
|
+
is_busy = false
|
505
|
+
kill_conn.list_processes.each do |row|
|
506
|
+
if row[0].to_i == mysql_tid && row[4] != 'Sleep'
|
507
|
+
$stderr.puts "Ctrl-C -- sending \"KILL QUERY #{mysql_tid}\" to server..."
|
508
|
+
kill_conn.kill(mysql_tid)
|
509
|
+
break
|
510
|
+
end
|
501
511
|
end
|
502
|
-
MySQLResults.conn.select_db(MySQLResults.database_name)
|
503
|
-
eval_context.call_init_registrations
|
504
|
-
else
|
505
|
-
$stderr.puts 'Ctrl-C -- exit!'
|
506
|
-
cmd_thread[:shutdown] = true
|
507
|
-
sleep(0.3)
|
508
|
-
cmd_thread.kill
|
509
512
|
end
|
513
|
+
loop{sleep 0x7fffffff}
|
510
514
|
end
|
511
515
|
|
516
|
+
child_pids << kill_pid
|
517
|
+
opts.delete_field(:mysql_password)
|
518
|
+
Signal.trap('INT', nil)
|
519
|
+
|
512
520
|
Signal.trap('CHLD') do
|
513
|
-
|
521
|
+
if ssh_pid && !opts.batch_output && !cmd_thread[:shutdown]
|
514
522
|
$stderr.puts "SSH child (#{ssh_pid}) stopped--shutting down..."
|
515
523
|
end
|
516
524
|
if MySQLResults.conn && MySQLResults.conn.busy?
|
data/lib/rsql.rb
CHANGED
data/lib/rsql/commands.rb
CHANGED
@@ -70,6 +70,7 @@ module RSQL
|
|
70
70
|
esc = ''
|
71
71
|
end
|
72
72
|
|
73
|
+
found_maps = false
|
73
74
|
if match_before_bang
|
74
75
|
new_bangs = {}
|
75
76
|
match.split(/\s*,\s*/).each do |ent|
|
@@ -78,20 +79,23 @@ module RSQL
|
|
78
79
|
# they are using a bang but have no maps
|
79
80
|
# so we assume this is a != or something
|
80
81
|
# similar and let it go through unmapped
|
81
|
-
|
82
|
+
match = match_before_bang + '!' + match
|
82
83
|
match_before_bang = nil
|
83
|
-
break
|
84
|
-
end
|
85
|
-
if val.strip == 'nil'
|
86
|
-
new_bangs[key.strip] = nil
|
87
84
|
else
|
88
|
-
|
85
|
+
found_maps = true
|
86
|
+
if val.strip == 'nil'
|
87
|
+
new_bangs[key.strip] = nil
|
88
|
+
else
|
89
|
+
new_bangs[key.strip] = val.to_sym
|
90
|
+
end
|
89
91
|
end
|
90
92
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
if found_maps
|
94
|
+
next unless match_before_bang
|
95
|
+
match = match_before_bang
|
96
|
+
match_before_bang = nil
|
97
|
+
bangs.merge!(new_bangs)
|
98
|
+
end
|
95
99
|
end
|
96
100
|
|
97
101
|
if sep == ?!
|
data/lib/rsql/eval_context.rb
CHANGED
File without changes
|
data/lib/rsql/mysql_results.rb
CHANGED
@@ -67,7 +67,9 @@ module RSQL
|
|
67
67
|
# implicitly resets the name cache.
|
68
68
|
#
|
69
69
|
def conn=(conn)
|
70
|
-
@@conn = conn
|
70
|
+
if @@conn = conn
|
71
|
+
@@conn.reconnect = true
|
72
|
+
end
|
71
73
|
reset_cache
|
72
74
|
end
|
73
75
|
|
@@ -197,6 +199,13 @@ module RSQL
|
|
197
199
|
# Get results from a query.
|
198
200
|
#
|
199
201
|
def query(sql, eval_context, raw=false, max_rows=@@max_rows)
|
202
|
+
if @@conn.reconnected?
|
203
|
+
# make sure we stick with the user's last database in case
|
204
|
+
# we had to reconnect (probably because the query thread was
|
205
|
+
# killed
|
206
|
+
@@conn.select_db(@@database_name) if @@database_name
|
207
|
+
end
|
208
|
+
|
200
209
|
@@history.shift if @@max_history <= @@history.size
|
201
210
|
@@history << sql
|
202
211
|
|
data/test/test_commands.rb
CHANGED
@@ -23,6 +23,7 @@ class TestCommands < Test::Unit::TestCase
|
|
23
23
|
@ctx = EvalContext.new
|
24
24
|
@conn = mock('Mysql')
|
25
25
|
@conn.expects(:list_dbs).returns([])
|
26
|
+
@conn.expects(:reconnect=)
|
26
27
|
MySQLResults.conn = @conn
|
27
28
|
end
|
28
29
|
|
@@ -42,6 +43,7 @@ class TestCommands < Test::Unit::TestCase
|
|
42
43
|
cmds = Commands.new('do some silly stuff', :display_by_column)
|
43
44
|
@conn.expects(:query).with(instance_of(String)).returns(nil)
|
44
45
|
@conn.expects(:affected_rows).returns(1)
|
46
|
+
@conn.expects(:reconnected?)
|
45
47
|
cmds.run!(@ctx)
|
46
48
|
assert_match(/Query OK, 1 row affected/, @strout.string)
|
47
49
|
end
|
@@ -61,25 +63,38 @@ class TestCommands < Test::Unit::TestCase
|
|
61
63
|
def test_multiple
|
62
64
|
@conn.expects(:query).with('one thing').returns(nil)
|
63
65
|
@conn.expects(:affected_rows).returns(1)
|
66
|
+
@conn.expects(:reconnected?)
|
64
67
|
cmds = Commands.new('. "one thing" ; . puts :hello.inspect', :display_by_column)
|
65
68
|
cmds.run!(@ctx)
|
66
69
|
assert_match(/^QueryOK,1rowaffected\(\d+.\d+sec\):hello$/,
|
67
70
|
@strout.string.gsub(/\s+/,''))
|
68
71
|
end
|
69
72
|
|
70
|
-
def
|
73
|
+
def test_bang
|
71
74
|
cmds = Commands.new('silly stuff ! this => that', :display_by_column)
|
72
75
|
@conn.expects(:query).with('silly stuff').returns(nil)
|
73
76
|
@conn.expects(:affected_rows).returns(13)
|
77
|
+
@conn.expects(:reconnected?)
|
74
78
|
cmds.run!(@ctx)
|
75
79
|
assert_match(/Query OK, 13 rows affected/, @strout.string)
|
80
|
+
end
|
76
81
|
|
77
|
-
|
82
|
+
def test_bang_with_no_map
|
78
83
|
cmds = Commands.new('silly stuff ! more things', :display_by_column)
|
79
84
|
@conn.expects(:query).with('silly stuff ! more things').returns(nil)
|
80
85
|
@conn.expects(:affected_rows).returns(4)
|
86
|
+
@conn.expects(:reconnected?)
|
81
87
|
cmds.run!(@ctx)
|
82
88
|
assert_match(/Query OK, 4 rows affected/, @strout.string)
|
83
89
|
end
|
84
90
|
|
91
|
+
def test_bang_with_pipe
|
92
|
+
cmds = Commands.new('silly stuff != this | puts @results.sql', :display_by_column)
|
93
|
+
@conn.expects(:query).with('silly stuff != this').returns(nil)
|
94
|
+
@conn.expects(:affected_rows).returns(21)
|
95
|
+
@conn.expects(:reconnected?)
|
96
|
+
cmds.run!(@ctx)
|
97
|
+
assert_match("silly stuff != this\n", @strout.string)
|
98
|
+
end
|
99
|
+
|
85
100
|
end # class TestCommands
|
data/test/test_eval_context.rb
CHANGED
@@ -22,6 +22,8 @@ class TestEvalContext < Test::Unit::TestCase
|
|
22
22
|
@conn.expects(:list_dbs).returns([])
|
23
23
|
@conn.expects(:query).with(instance_of(String)).returns(nil)
|
24
24
|
@conn.expects(:affected_rows).returns(0)
|
25
|
+
@conn.expects(:reconnect=)
|
26
|
+
@conn.expects(:reconnected?)
|
25
27
|
MySQLResults.conn = @conn
|
26
28
|
@ctx = EvalContext.new
|
27
29
|
@ctx.load(File.join(File.dirname(__FILE__),'..','example.rsqlrc'))
|
@@ -32,6 +34,7 @@ class TestEvalContext < Test::Unit::TestCase
|
|
32
34
|
$stdout = out = StringIO.new
|
33
35
|
@conn.expects(:query).with(instance_of(String)).returns(nil)
|
34
36
|
@conn.expects(:affected_rows).returns(0)
|
37
|
+
@conn.expects(:reconnected?)
|
35
38
|
@ctx.safe_eval('reload', nil, out)
|
36
39
|
assert_match(/loaded: .+?example.rsqlrc/, out.string)
|
37
40
|
ensure
|
data/test/test_mysql_results.rb
CHANGED
@@ -24,6 +24,7 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
24
24
|
def test_databases
|
25
25
|
assert_equal([], MySQLResults.databases)
|
26
26
|
conn = mock('Mysql')
|
27
|
+
conn.expects(:reconnect=)
|
27
28
|
conn.expects(:list_dbs).returns(['accounts'])
|
28
29
|
conn.expects(:select_db)
|
29
30
|
conn.expects(:list_tables).returns([])
|
@@ -36,6 +37,7 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
36
37
|
MySQLResults.reset_cache
|
37
38
|
|
38
39
|
conn = mock('Mysql')
|
40
|
+
conn.expects(:reconnect=)
|
39
41
|
conn.expects(:list_dbs).returns(['accounts'])
|
40
42
|
conn.expects(:select_db)
|
41
43
|
conn.expects(:list_tables).returns(['users','groups'])
|
@@ -51,6 +53,7 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
51
53
|
assert_equal([], MySQLResults.complete(nil))
|
52
54
|
|
53
55
|
conn = mock('Mysql')
|
56
|
+
conn.expects(:reconnect=)
|
54
57
|
conn.expects(:list_dbs).returns(['Accounts','Devices','Locations'])
|
55
58
|
conn.expects(:select_db).times(3)
|
56
59
|
tbls = sequence(:tbls)
|
@@ -87,9 +90,11 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
87
90
|
res.expects(:fetch_row).in_sequence(rows).returns(nil)
|
88
91
|
|
89
92
|
conn = mock('Mysql')
|
93
|
+
conn.expects(:reconnect=)
|
90
94
|
conn.expects(:list_dbs).returns([])
|
91
95
|
conn.expects(:query).with(instance_of(String)).returns(res)
|
92
96
|
conn.expects(:affected_rows).returns(1)
|
97
|
+
conn.expects(:reconnected?)
|
93
98
|
MySQLResults.conn = conn
|
94
99
|
|
95
100
|
bangs = mock('bangs')
|
@@ -145,9 +150,11 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
145
150
|
res.expects(:fetch_row).in_sequence(rows).returns(nil)
|
146
151
|
|
147
152
|
conn = mock('Mysql')
|
153
|
+
conn.expects(:reconnect=)
|
148
154
|
conn.stubs(:list_dbs).returns([])
|
149
155
|
conn.stubs(:query).with(instance_of(String)).returns(res)
|
150
156
|
conn.stubs(:affected_rows).returns(1)
|
157
|
+
conn.expects(:reconnected?).times(4)
|
151
158
|
MySQLResults.reset_history
|
152
159
|
MySQLResults.conn = conn
|
153
160
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 11
|
10
|
+
version: 0.2.11
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brad Robel-Forrest
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-02-
|
18
|
+
date: 2012-02-24 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -124,7 +124,6 @@ files:
|
|
124
124
|
- bin/rsql
|
125
125
|
- example.rsqlrc
|
126
126
|
- example.rsqlrc.rdoc
|
127
|
-
- extra/mysql-client-5.1.59-1.tgz
|
128
127
|
- lib/rsql.rb
|
129
128
|
- lib/rsql/commands.rb
|
130
129
|
- lib/rsql/eval_context.rb
|
@@ -166,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
165
|
requirements: []
|
167
166
|
|
168
167
|
rubyforge_project:
|
169
|
-
rubygems_version: 1.8.
|
168
|
+
rubygems_version: 1.8.17
|
170
169
|
signing_key:
|
171
170
|
specification_version: 3
|
172
171
|
summary: Ruby-based MySQL command line with recipes.
|
Binary file
|