rsql 0.2.0 → 0.2.1
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 +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
|