rsql 0.2.10 → 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
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
- at_exit do
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
- File.open(ipc_fn,'r'){|f| ipc_state = f.gets.strip}
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
- # keep a secondary connection to allow us to kill off a running query
488
- kill_conn = Mysql.new(opts.mysql_host, opts.mysql_user, opts.mysql_password,
489
- MySQLResults.database_name, opts.mysql_port)
490
- opts.delete_field(:mysql_password)
491
-
492
- Signal.trap('INT') do
493
- # emulate MySQL's behavior
494
- if MySQLResults.conn && MySQLResults.conn.busy?
495
- tid = MySQLResults.conn.thread_id
496
- $stderr.puts "Ctrl-C -- sending \"KILL QUERY #{tid}\" to server..."
497
- kill_conn.kill(tid)
498
- 10.times do
499
- break unless MySQLResults.conn.busy?
500
- sleep(0.5)
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
- unless opts.batch_output
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?
@@ -5,7 +5,7 @@
5
5
  # information.
6
6
  #
7
7
  module RSQL
8
- VERSION = '0.2.10'
8
+ VERSION = '0.2.11'
9
9
 
10
10
  require 'rsql/mysql_results'
11
11
  require 'rsql/eval_context'
@@ -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
- esc = match_before_bang + '!' + match
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
- new_bangs[key.strip] = val.to_sym
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
- next unless match_before_bang
92
- match = match_before_bang
93
- match_before_bang = nil
94
- bangs.merge!(new_bangs)
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 == ?!
File without changes
@@ -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
 
@@ -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 test_bangs
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
- # now test logic to continue if it _doesn't_ look like a bang
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
@@ -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
@@ -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: 3
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 10
10
- version: 0.2.10
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 00:00:00 Z
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.16
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.