rsql 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -63,7 +63,7 @@ _user_, _password_, or _port_ values using the following syntax:
63
63
  [<user>[:<password>]@]<host>[:<port>]
64
64
 
65
65
  Once at the +rsql+ prompt, normal MySQL queries can be entered as
66
- expected, ending each with a semicolon (;) for columnar output or \G
66
+ expected, ending each with a semicolon (;) for columnar output or \\G
67
67
  for line-by-line output formatting.
68
68
 
69
69
  Ruby commands will be evaluated for any content entered at the RSQL
@@ -77,14 +77,14 @@ the colon and the at sign):
77
77
 
78
78
  rsql root:@127.0.0.1
79
79
 
80
- Connect as the "readonly" user to the "internal.database.com" host's
81
- MySQL server after establishing a SSH tunnel to the
80
+ Connect as the "readonly" user to the "internal.database.acme.com"
81
+ host's MySQL server after establishing a SSH tunnel to the
82
82
  "external.acme.com" gateway. In this case, we are either expecting
83
83
  that our SSH configuration is set up with the right user name. Because
84
84
  we did not provide a password for MySQL, one will be obtained directly
85
85
  from the console (without echoing the characters typed):
86
86
 
87
- rsql -ssh external.acme.com readonly@database.acme.com
87
+ rsql -ssh external.acme.com readonly@internal.database.acme.com
88
88
 
89
89
  == GETTING STARTED
90
90
 
data/bin/rsql CHANGED
@@ -263,7 +263,7 @@ if ssh_host
263
263
  File.open(ipc_fn,'w'){|f| f.puts('start')}
264
264
  ssh_enabled = false
265
265
  Signal.trap('INT') do
266
- $stderr.puts 'Shutting down...'
266
+ $stderr.puts 'Shutting down...' unless batch_input
267
267
  ssh_enabled = false
268
268
  end
269
269
  opts = {:timeout => 15}
@@ -279,7 +279,7 @@ if ssh_host
279
279
  opts[:password] = ssh_password if ssh_password
280
280
  ssh = Net::SSH.start(ssh_host, ssh_user, opts)
281
281
  ssh_enabled = true
282
- printf "connected (#{$$})..."
282
+ printf "connected (#{$$})..." unless batch_input
283
283
  $stdout.flush
284
284
  rescue Net::SSH::AuthenticationFailed
285
285
  if 2 < password_retry_cnt
@@ -295,7 +295,9 @@ if ssh_host
295
295
  end
296
296
  if ssh_enabled
297
297
  ssh.forward.local(mysql_port, mysql_host, remote_mysql_port)
298
- puts(verbose ? "ready (#{mysql_port} => #{remote_mysql_port})" : 'ready')
298
+ unless batch_input
299
+ puts(verbose ? "ready (#{mysql_port} => #{remote_mysql_port})" : 'ready')
300
+ end
299
301
  File.open(ipc_fn,'w'){|f| f.puts('ready')}
300
302
  ssh.loop(1) { ssh_enabled }
301
303
  end
@@ -330,7 +332,7 @@ mysql_conn = "#{mysql_host}:#{remote_mysql_port || mysql_port}"
330
332
  begin
331
333
  MySQLResults.conn = Mysql.new(mysql_host, mysql_user, mysql_password,
332
334
  MySQLResults.database_name, mysql_port)
333
- puts 'connected'
335
+ puts 'connected' unless batch_input
334
336
  rescue Mysql::Error => ex
335
337
  if ex.message.include?('Client does not support authentication')
336
338
  $stderr.puts "failed to connect to #{mysql_conn} mysql server: unknown credentials?"
@@ -387,7 +389,7 @@ cmd_thread = Thread.new do
387
389
  break
388
390
  end
389
391
  input << str
390
- break if input =~ /([^\\];|\\G)\s*$/
392
+ break if input =~ /([^\\];|\\G)\s*$/ || input =~ /^\s*use\s+\w+\s*$/
391
393
  # make sure we separate the lines with some whitespace if
392
394
  # they didn't
393
395
  input << ' ' unless str =~ /\s$/
@@ -2,7 +2,7 @@
2
2
  # Commands using an EvalContext for handling recipes.
3
3
  #
4
4
  module RSQL
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.1'
6
6
 
7
7
  require 'rsql/mysql_results'
8
8
  require 'rsql/eval_context'
@@ -101,6 +101,7 @@ module RSQL
101
101
  end
102
102
 
103
103
  def reload
104
+ MySQLResults.reset_cache
104
105
  @loaded_fns.each{|fn| self.load(fn, false)}
105
106
  puts "loaded: #{@loaded_fns.inspect}"
106
107
  end
@@ -191,6 +192,7 @@ module RSQL
191
192
  end
192
193
 
193
194
  ret = MySQLResults.complete(str)
195
+
194
196
  ret += @registrations.keys.sort_by{|sym|sym.to_s}.collect do |sym|
195
197
  name = sym.to_s
196
198
  if name.start_with?(str)
@@ -213,6 +215,8 @@ module RSQL
213
215
  # Convert a binary string value into a hexadecimal string.
214
216
  #
215
217
  def to_hexstr(bin, limit=@hexstr_limit, prefix='0x')
218
+ return bin if bin.nil?
219
+
216
220
  cnt = 0
217
221
  str = prefix << bin.gsub(/./m) do |ch|
218
222
  if limit
@@ -37,7 +37,7 @@ module RSQL
37
37
  @@field_separator = ' '
38
38
  @@max_rows = 1000
39
39
  @@database_name = nil
40
- @@databases = nil
40
+ @@name_cache = {}
41
41
 
42
42
  class MaxRowsException < RangeError
43
43
  def initialize(rows, max)
@@ -49,13 +49,36 @@ module RSQL
49
49
 
50
50
  class << self
51
51
 
52
+ # Get the underlying MySQL connection object in use.
53
+ #
52
54
  def conn; @@conn; end
53
- def conn=(conn); @@conn = conn; end
54
55
 
56
+ # Set the underlying MySQL connection object to use which
57
+ # implicitly resets the name cache.
58
+ #
59
+ def conn=(conn)
60
+ @@conn = conn
61
+ reset_cache
62
+ end
63
+
64
+ # Get the field separator to use when writing rows in
65
+ # columns.
66
+ #
55
67
  def field_separator; @@field_separator; end
68
+
69
+ # Set the field separator to use when writing rows in
70
+ # columns.
71
+ #
56
72
  def field_separator=(sep); @@field_separator = sep; end
57
73
 
74
+ # Get the maximum number of rows to process before
75
+ # throwing a MaxRowsException.
76
+ #
58
77
  def max_rows; @@max_rows; end
78
+
79
+ # Set the maximum number of rows to process before
80
+ # throwing a MaxRowsException.
81
+ #
59
82
  def max_rows=(cnt); @@max_rows = cnt; end
60
83
 
61
84
  # Get the name of the current database in use.
@@ -69,72 +92,72 @@ module RSQL
69
92
  # Get the list of databases available.
70
93
  #
71
94
  def databases
72
- if @@conn && @@databases.nil?
73
- @@databases = @@conn.list_dbs.sort
74
- end
75
- @@databases
95
+ @@name_cache.keys.sort
76
96
  end
77
97
 
78
- # Force the table cache to be reread on the next request
79
- # for tables.
98
+ # Get the list of tables available for the current
99
+ # database or a specific one.
80
100
  #
81
- def reset_cache
82
- @@databases = nil
83
- @@last_table_list = Hash.new{|h,k| h[k] = [Time.at(0), []]}
101
+ def tables(database=@@database_name)
102
+ @@name_cache[database] || []
84
103
  end
85
104
 
86
- # Get the list of tables available (if a database is
87
- # selected) at most once every ten seconds.
105
+ # Force the database and table names cache to be (re)loaded.
88
106
  #
89
- def tables(database=@@database_name)
90
- now = Time.now
91
- (last, tnames) = @@last_table_list[database]
92
- if last + 10 < now
93
- begin
94
- if @@conn
95
- if database && database != @@database_name
96
- tnames = @@conn.list_tables("FROM #{database}").sort
97
- else
98
- tnames = @@conn.list_tables.sort
99
- end
107
+ def reset_cache
108
+ @@name_cache = {}
109
+ begin
110
+ if @@conn
111
+ @@conn.list_dbs.each do |db_name|
112
+ @@conn.select_db(db_name)
113
+ @@name_cache[db_name] = @@conn.list_tables.sort
100
114
  end
101
- rescue Mysql::Error => ex
102
- tnames = []
103
115
  end
104
- @@last_table_list[database] = [now, tnames]
116
+ rescue Mysql::Error => ex
117
+ ensure
118
+ if @@conn && @@database_name
119
+ @@conn.select_db(@@database_name)
120
+ end
105
121
  end
106
- tnames
107
122
  end
108
123
 
109
124
  # Provide a list of tab completions given the prompted
110
- # value.
125
+ # case-insensitive value.
111
126
  #
112
127
  def complete(str)
113
128
  return [] unless @@conn
114
129
 
130
+ ret = []
131
+
115
132
  # offer table names from a specific database
116
133
  if str =~ /^([^.]+)\.(.*)$/
117
134
  db = $1
118
135
  tb = $2
119
- ret = tables(db).collect do |n|
120
- if n.downcase.start_with?(tb)
121
- "#{db}.#{n}"
122
- else
123
- nil
136
+ @@name_cache.each do |db_name, tnames|
137
+ if db.casecmp(db_name) == 0
138
+ tnames.each do |n|
139
+ if m = n.match(/^(#{tb})/i)
140
+ ret << "#{db_name}.#{n}"
141
+ end
142
+ end
143
+ break
124
144
  end
125
145
  end
126
- ret.compact!
127
146
  else
128
- ret = databases.select{|n| n != @@database_name && n.downcase.start_with?(str)}
129
- if @@database_name
130
- # if we've selected a db then we want to offer
131
- # completions for other dbs as well as tables for
132
- # the currently selected db
133
- ret += tables.select{|n| n.downcase.start_with?(str)}
147
+ @@name_cache.each do |db_name, tnames|
148
+ if db_name == @@database_name
149
+ tnames.each do |n|
150
+ if m = n.match(/^(#{str})/i)
151
+ ret << n
152
+ end
153
+ end
154
+ elsif m = db_name.match(/^(#{str})/i)
155
+ ret << db_name
156
+ end
134
157
  end
135
158
  end
136
159
 
137
- return ret
160
+ return ret.sort
138
161
  end
139
162
 
140
163
  # Get results from a query.
@@ -200,9 +223,6 @@ module RSQL
200
223
 
201
224
  end # class << self
202
225
 
203
- # init the cache
204
- reset_cache
205
-
206
226
  ########################################
207
227
 
208
228
  def initialize(sql, elapsed, affected_rows,
@@ -21,7 +21,9 @@ class TestCommands < Test::Unit::TestCase
21
21
  @orig_stdout = $stdout
22
22
  $stdout = @strout = StringIO.new
23
23
  @ctx = EvalContext.new
24
- @conn = MySQLResults.conn = mock('Mysql')
24
+ @conn = mock('Mysql')
25
+ @conn.expects(:list_dbs).returns([])
26
+ MySQLResults.conn = @conn
25
27
  end
26
28
 
27
29
  def teardown
@@ -18,14 +18,17 @@ class TestEvalContext < Test::Unit::TestCase
18
18
  include RSQL
19
19
 
20
20
  def setup
21
- @conn = MySQLResults.conn = mock('Mysql')
21
+ @conn = mock('Mysql')
22
+ @conn.expects(:list_dbs).returns([])
22
23
  @conn.expects(:query).with(instance_of(String)).returns(nil)
23
24
  @conn.expects(:affected_rows).returns(0)
25
+ MySQLResults.conn = @conn
24
26
  @ctx = EvalContext.new
25
27
  @ctx.load(File.join(File.dirname(__FILE__),'..','example.rsqlrc'))
26
28
  end
27
29
 
28
30
  def test_load
31
+ @conn.expects(:list_dbs).returns([])
29
32
  orig = $stdout
30
33
  $stdout = out = StringIO.new
31
34
  @ctx.safe_eval('reload', nil, out)
@@ -108,7 +111,6 @@ class TestEvalContext < Test::Unit::TestCase
108
111
  end
109
112
 
110
113
  def test_complete
111
- @conn.expects(:list_dbs).returns([])
112
114
  assert_equal(18, @ctx.complete('').size)
113
115
  assert_equal(['version'], @ctx.complete('v'))
114
116
  assert_equal(['.version'], @ctx.complete('.v'))
@@ -22,9 +22,11 @@ class TestMySQLResults < Test::Unit::TestCase
22
22
  end
23
23
 
24
24
  def test_databases
25
- assert_equal(nil, MySQLResults.databases)
25
+ assert_equal([], MySQLResults.databases)
26
26
  conn = mock('Mysql')
27
27
  conn.expects(:list_dbs).returns(['accounts'])
28
+ conn.expects(:select_db)
29
+ conn.expects(:list_tables).returns([])
28
30
  MySQLResults.conn = conn
29
31
  assert_equal(['accounts'], MySQLResults.databases)
30
32
  end
@@ -34,31 +36,37 @@ class TestMySQLResults < Test::Unit::TestCase
34
36
  MySQLResults.reset_cache
35
37
 
36
38
  conn = mock('Mysql')
39
+ conn.expects(:list_dbs).returns(['accounts'])
40
+ conn.expects(:select_db)
37
41
  conn.expects(:list_tables).returns(['users','groups'])
38
42
  MySQLResults.conn = conn
39
- assert_equal(['groups','users'], MySQLResults.tables)
40
- MySQLResults.reset_cache
41
43
 
42
- conn.expects(:list_tables).with(instance_of(String)).returns(['prefs'])
43
- assert_equal(['prefs'], MySQLResults.tables('accounts'))
44
+ assert_equal([], MySQLResults.tables)
45
+ MySQLResults.database_name = 'accounts'
46
+ assert_equal(['groups','users'], MySQLResults.tables)
47
+ assert_equal(['groups','users'], MySQLResults.tables('accounts'))
44
48
  end
45
49
 
46
50
  def test_complete
47
51
  assert_equal([], MySQLResults.complete(nil))
48
52
 
49
53
  conn = mock('Mysql')
50
- conn.expects(:list_dbs).returns(['accounts','devices','locations'])
54
+ conn.expects(:list_dbs).returns(['Accounts','Devices','Locations'])
55
+ conn.expects(:select_db).times(3)
56
+ tbls = sequence(:tbls)
57
+ conn.expects(:list_tables).in_sequence(tbls).returns(['Prefs','Names'])
58
+ conn.expects(:list_tables).in_sequence(tbls).returns(['IPs'])
59
+ conn.expects(:list_tables).in_sequence(tbls).returns(['Street','City','State'])
51
60
  MySQLResults.conn = conn
52
61
 
53
- assert_equal(['accounts','devices','locations'], MySQLResults.complete(''))
54
- assert_equal(['accounts'], MySQLResults.complete('a'))
62
+ assert_equal(['Accounts','Devices','Locations'], MySQLResults.complete(''))
63
+ assert_equal(['Accounts'], MySQLResults.complete('a'))
55
64
 
56
- MySQLResults.database_name = 'accounts'
57
- conn.expects(:list_tables).returns(['prefs','names'])
58
- assert_equal(['devices','locations','names','prefs'], MySQLResults.complete(''))
59
- assert_equal(['names'], MySQLResults.complete('n'))
65
+ MySQLResults.database_name = 'Accounts'
66
+ assert_equal(['Devices','Locations','Names','Prefs'], MySQLResults.complete(''))
67
+ assert_equal(['Names'], MySQLResults.complete('n'))
60
68
 
61
- assert_equal(['accounts.names','accounts.prefs'], MySQLResults.complete('accounts.'))
69
+ assert_equal(['Accounts.Names','Accounts.Prefs'], MySQLResults.complete('accounts.'))
62
70
  end
63
71
 
64
72
  def test_query
@@ -79,6 +87,7 @@ class TestMySQLResults < Test::Unit::TestCase
79
87
  res.expects(:fetch_row).in_sequence(rows).returns(nil)
80
88
 
81
89
  conn = mock('Mysql')
90
+ conn.expects(:list_dbs).returns([])
82
91
  conn.expects(:query).with(instance_of(String)).returns(res)
83
92
  conn.expects(:affected_rows).returns(1)
84
93
  MySQLResults.conn = conn
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: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
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: 2011-09-18 00:00:00 Z
18
+ date: 2011-09-24 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: net-ssh
@@ -124,6 +124,7 @@ files:
124
124
  - README.rdoc
125
125
  - bin/rsql
126
126
  - example.rsqlrc
127
+ - extra/mysql-client-5.1.59-1.tgz
127
128
  - lib/rsql.rb
128
129
  - lib/rsql/commands.rb
129
130
  - lib/rsql/eval_context.rb