rsql 0.2.10 → 0.2.11
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/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
|