rsql 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -4
- data/bin/rsql +7 -5
- data/extra/mysql-client-5.1.59-1.tgz +0 -0
- data/lib/rsql.rb +1 -1
- data/lib/rsql/eval_context.rb +4 -0
- data/lib/rsql/mysql_results.rb +65 -45
- data/test/test_commands.rb +3 -1
- data/test/test_eval_context.rb +4 -2
- data/test/test_mysql_results.rb +22 -13
- metadata +5 -4
data/README.rdoc
CHANGED
@@ -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
|
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"
|
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
|
-
|
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$/
|
Binary file
|
data/lib/rsql.rb
CHANGED
data/lib/rsql/eval_context.rb
CHANGED
@@ -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
|
data/lib/rsql/mysql_results.rb
CHANGED
@@ -37,7 +37,7 @@ module RSQL
|
|
37
37
|
@@field_separator = ' '
|
38
38
|
@@max_rows = 1000
|
39
39
|
@@database_name = nil
|
40
|
-
@@
|
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
|
-
|
73
|
-
@@databases = @@conn.list_dbs.sort
|
74
|
-
end
|
75
|
-
@@databases
|
95
|
+
@@name_cache.keys.sort
|
76
96
|
end
|
77
97
|
|
78
|
-
#
|
79
|
-
#
|
98
|
+
# Get the list of tables available for the current
|
99
|
+
# database or a specific one.
|
80
100
|
#
|
81
|
-
def
|
82
|
-
@@
|
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
|
-
#
|
87
|
-
# selected) at most once every ten seconds.
|
105
|
+
# Force the database and table names cache to be (re)loaded.
|
88
106
|
#
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
120
|
-
if
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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,
|
data/test/test_commands.rb
CHANGED
@@ -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 =
|
24
|
+
@conn = mock('Mysql')
|
25
|
+
@conn.expects(:list_dbs).returns([])
|
26
|
+
MySQLResults.conn = @conn
|
25
27
|
end
|
26
28
|
|
27
29
|
def teardown
|
data/test/test_eval_context.rb
CHANGED
@@ -18,14 +18,17 @@ class TestEvalContext < Test::Unit::TestCase
|
|
18
18
|
include RSQL
|
19
19
|
|
20
20
|
def setup
|
21
|
-
@conn =
|
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'))
|
data/test/test_mysql_results.rb
CHANGED
@@ -22,9 +22,11 @@ class TestMySQLResults < Test::Unit::TestCase
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_databases
|
25
|
-
assert_equal(
|
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
|
-
|
43
|
-
|
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(['
|
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(['
|
54
|
-
assert_equal(['
|
62
|
+
assert_equal(['Accounts','Devices','Locations'], MySQLResults.complete(''))
|
63
|
+
assert_equal(['Accounts'], MySQLResults.complete('a'))
|
55
64
|
|
56
|
-
MySQLResults.database_name = '
|
57
|
-
|
58
|
-
assert_equal(['
|
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(['
|
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:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
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
|
+
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
|