rsql 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rsql +81 -57
- metadata +6 -16
- data/LICENSE +0 -19
- data/example.rsqlrc +0 -291
- data/extra/mysql-client-5.1.59-1.tgz +0 -0
- data/lib/rsql.rb +0 -10
- data/lib/rsql/commands.rb +0 -243
- data/lib/rsql/eval_context.rb +0 -690
- data/lib/rsql/mysql_results.rb +0 -410
- data/test/test_commands.rb +0 -85
- data/test/test_eval_context.rb +0 -179
- data/test/test_mysql_results.rb +0 -130
data/bin/rsql
CHANGED
@@ -20,6 +20,9 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
+
# todo: look into using the logger class
|
24
|
+
# (http://ruby-doc.org/stdlib-1.8.7/libdoc/logger/rdoc/Logger.html)
|
25
|
+
|
23
26
|
begin
|
24
27
|
# this isn't required unless that's how mysql and net/ssh have
|
25
28
|
# been installed
|
@@ -27,6 +30,7 @@ begin
|
|
27
30
|
rescue LoadError
|
28
31
|
end
|
29
32
|
|
33
|
+
require 'ostruct'
|
30
34
|
require 'tmpdir'
|
31
35
|
require 'thread'
|
32
36
|
require 'timeout'
|
@@ -50,7 +54,8 @@ include RSQL
|
|
50
54
|
|
51
55
|
bn = File.basename($0, '.rb')
|
52
56
|
|
53
|
-
|
57
|
+
opts = OpenStruct.new
|
58
|
+
eval_context = EvalContext.new(opts)
|
54
59
|
|
55
60
|
# rewrite all double hyphen options into singles so both are supported
|
56
61
|
ARGV.map!{|a| a.sub(/^--/,'-')}
|
@@ -132,8 +137,8 @@ if ARGV.delete('-version')
|
|
132
137
|
exit
|
133
138
|
end
|
134
139
|
|
135
|
-
verbose = ARGV.delete('-verbose')
|
136
|
-
eval_context.verbose = verbose
|
140
|
+
opts.verbose = !ARGV.delete('-verbose').nil?
|
141
|
+
eval_context.verbose = opts.verbose
|
137
142
|
|
138
143
|
if i = ARGV.index('-maxrows')
|
139
144
|
ARGV.delete_at(i)
|
@@ -144,7 +149,7 @@ if i = ARGV.index('-batch')
|
|
144
149
|
ARGV.delete_at(i)
|
145
150
|
MySQLResults.field_separator = ARGV.delete_at(i)
|
146
151
|
MySQLResults.field_separator = "\t" if MySQLResults.field_separator == '\t'
|
147
|
-
batch_output = true
|
152
|
+
opts.batch_output = true
|
148
153
|
end
|
149
154
|
|
150
155
|
if i = ARGV.index('-ssh')
|
@@ -152,22 +157,24 @@ if i = ARGV.index('-ssh')
|
|
152
157
|
|
153
158
|
ARGV.delete_at(i)
|
154
159
|
|
155
|
-
(ssh_host, ssh_user, ssh_password, ssh_port) =
|
160
|
+
(opts.ssh_host, opts.ssh_user, opts.ssh_password, opts.ssh_port) =
|
161
|
+
split_login(ARGV.delete_at(i))
|
156
162
|
|
157
163
|
default_config = File.join(ENV['HOME'], '.ssh', 'config')
|
158
164
|
if File.exists?(default_config)
|
159
|
-
|
165
|
+
opts.ssh_config = default_config
|
166
|
+
opts.ssh_cfghash = Net::SSH::Config.load(default_config, opts.ssh_host)
|
160
167
|
end
|
161
168
|
|
162
|
-
if ssh_user.nil? || ssh_user.empty?
|
163
|
-
ssh_user = ssh_cfghash['user'] || ENV['USER'] || ENV['USERNAME']
|
169
|
+
if opts.ssh_user.nil? || opts.ssh_user.empty?
|
170
|
+
opts.ssh_user = opts.ssh_cfghash['user'] || ENV['USER'] || ENV['USERNAME']
|
164
171
|
end
|
165
172
|
end
|
166
173
|
|
167
174
|
if i = ARGV.index('-sshconfig')
|
168
175
|
ARGV.delete_at(i)
|
169
|
-
ssh_config = ARGV.delete_at(i)
|
170
|
-
ssh_cfghash = Net::SSH::Config.load(ssh_config, ssh_host)
|
176
|
+
opts.ssh_config = ARGV.delete_at(i)
|
177
|
+
opts.ssh_cfghash = Net::SSH::Config.load(opts.ssh_config, opts.ssh_host)
|
171
178
|
end
|
172
179
|
|
173
180
|
if i = ARGV.index('-e')
|
@@ -212,16 +219,18 @@ USAGE
|
|
212
219
|
exit 1
|
213
220
|
end
|
214
221
|
|
215
|
-
(mysql_host, mysql_user, mysql_password, mysql_port) =
|
216
|
-
|
217
|
-
|
222
|
+
(opts.mysql_host, opts.mysql_user, opts.mysql_password, opts.mysql_port) =
|
223
|
+
split_login(ARGV.shift)
|
224
|
+
opts.mysql_password ||=
|
225
|
+
get_password("#{opts.mysql_user}@#{opts.mysql_host} MySQL password: ")
|
226
|
+
opts.remote_mysql_host = opts.mysql_host
|
218
227
|
|
219
|
-
if ssh_host
|
228
|
+
if opts.ssh_host
|
220
229
|
# randomly pick a tcp port above 1024
|
221
|
-
remote_mysql_port = mysql_port || 3306
|
222
|
-
mysql_port = rand(0xffff-1025) + 1025
|
223
|
-
elsif mysql_port.nil?
|
224
|
-
mysql_port = 3306
|
230
|
+
opts.remote_mysql_port = opts.mysql_port || 3306
|
231
|
+
opts.mysql_port = rand(0xffff-1025) + 1025
|
232
|
+
elsif opts.mysql_port.nil?
|
233
|
+
opts.mysql_port = 3306
|
225
234
|
end
|
226
235
|
|
227
236
|
MySQLResults.database_name = ARGV.shift
|
@@ -259,9 +268,9 @@ rescue Exception => ex
|
|
259
268
|
false
|
260
269
|
end
|
261
270
|
|
262
|
-
MySQLResults.max_rows ||= batch_output ? 5000 : 1000
|
271
|
+
MySQLResults.max_rows ||= opts.batch_output ? 5000 : 1000
|
263
272
|
|
264
|
-
if ssh_host
|
273
|
+
if opts.ssh_host
|
265
274
|
|
266
275
|
# might need to open an idle channel here so server doesn't close on
|
267
276
|
# us...or just loop reconnection here in the thread...
|
@@ -269,7 +278,7 @@ if ssh_host
|
|
269
278
|
password_retry_cnt = 0
|
270
279
|
|
271
280
|
unless batch_input
|
272
|
-
print "SSH #{ssh_user}#{ssh_user ? '@' : ''}#{ssh_host}..."
|
281
|
+
print "SSH #{opts.ssh_user}#{opts.ssh_user ? '@' : ''}#{opts.ssh_host}..."
|
273
282
|
$stdout.flush
|
274
283
|
end
|
275
284
|
|
@@ -291,16 +300,18 @@ if ssh_host
|
|
291
300
|
$stderr.puts 'Closing SSH connection...' unless batch_input
|
292
301
|
ssh_enabled = false
|
293
302
|
end
|
294
|
-
|
295
|
-
|
296
|
-
if verbose
|
297
|
-
|
298
|
-
puts "SSH options: #{
|
299
|
-
puts "SSH config: #{ssh_cfghash.inspect}"
|
303
|
+
ssh_opts = {:timeout => 15}
|
304
|
+
ssh_opts[:config] = opts.ssh_config if opts.ssh_config
|
305
|
+
if opts.verbose
|
306
|
+
ssh_opts[:verbose] = :debug
|
307
|
+
puts "SSH options: #{ssh_opts.inspect}"
|
308
|
+
puts "SSH config: #{opts.ssh_cfghash.inspect}"
|
300
309
|
end
|
301
310
|
begin
|
302
|
-
|
303
|
-
|
311
|
+
ssh_opts[:password] = opts.ssh_password if opts.ssh_password
|
312
|
+
opts.delete_field(:ssh_password)
|
313
|
+
ssh = Net::SSH.start(opts.ssh_host, opts.ssh_user, ssh_opts)
|
314
|
+
ssh_opts.delete(:password)
|
304
315
|
ssh_enabled = true
|
305
316
|
printf "connected (#{$$})..." unless batch_input
|
306
317
|
$stdout.flush
|
@@ -308,20 +319,26 @@ if ssh_host
|
|
308
319
|
if 2 < password_retry_cnt
|
309
320
|
$stderr.puts 'Permission denied. Giving up.'
|
310
321
|
else
|
311
|
-
$stderr.puts 'Permission denied, please try again.' if ssh_password
|
312
|
-
ssh_password = get_password("#{ssh_user}@#{ssh_host} SSH password: ")
|
313
|
-
unless ssh_password.empty?
|
322
|
+
$stderr.puts 'Permission denied, please try again.' if opts.ssh_password
|
323
|
+
opts.ssh_password = get_password("#{opts.ssh_user}@#{opts.ssh_host} SSH password: ")
|
324
|
+
unless opts.ssh_password.empty?
|
314
325
|
password_retry_cnt += 1
|
315
326
|
retry
|
316
327
|
end
|
317
328
|
end
|
318
329
|
rescue Timeout::Error => ex
|
319
330
|
$stderr.puts ex.message
|
331
|
+
rescue Exception => ex
|
332
|
+
if opts.verbose
|
333
|
+
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
334
|
+
else
|
335
|
+
$stderr.puts(ex.message)
|
336
|
+
end
|
320
337
|
ensure
|
321
338
|
if ssh_enabled
|
322
|
-
ssh.forward.local(mysql_port, mysql_host, remote_mysql_port)
|
339
|
+
ssh.forward.local(opts.mysql_port, opts.mysql_host, opts.remote_mysql_port)
|
323
340
|
unless batch_input
|
324
|
-
puts(verbose ? "ready (#{mysql_port} => #{remote_mysql_port})" : 'ready')
|
341
|
+
puts(opts.verbose ? "ready (#{opts.mysql_port} => #{opts.remote_mysql_port})" : 'ready')
|
325
342
|
end
|
326
343
|
File.open(ipc_fn,'w'){|f| f.puts('ready')}
|
327
344
|
ssh.loop(1) { ssh_enabled }
|
@@ -343,9 +360,14 @@ if ssh_host
|
|
343
360
|
sleep 1
|
344
361
|
end
|
345
362
|
Process.kill('KILL', ssh_pid) unless killed
|
363
|
+
rescue Errno::ESRCH
|
364
|
+
# do nothing, process is already gone
|
346
365
|
rescue Exception => ex
|
347
|
-
|
348
|
-
|
366
|
+
if opts.verbose
|
367
|
+
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
368
|
+
else
|
369
|
+
$stderr.puts(ex.message)
|
370
|
+
end
|
349
371
|
end
|
350
372
|
end
|
351
373
|
File.unlink(ipc_fn) if File.exists?(ipc_fn)
|
@@ -363,24 +385,23 @@ if ssh_host
|
|
363
385
|
unless ipc_state == 'ready'
|
364
386
|
# give the child time to exit
|
365
387
|
sleep(0.5)
|
366
|
-
$stderr.puts "failed to connect to #{ssh_host} SSH host"
|
388
|
+
$stderr.puts "failed to connect to #{opts.ssh_host} SSH host"
|
367
389
|
exit 1
|
368
390
|
end
|
369
391
|
|
370
392
|
# now have our mysql connection use our port forward...
|
371
|
-
mysql_host = '127.0.0.1'
|
393
|
+
opts.mysql_host = '127.0.0.1'
|
372
394
|
end
|
373
395
|
|
374
396
|
unless batch_input
|
375
|
-
print "MySQL #{mysql_user}@#{
|
397
|
+
print "MySQL #{opts.mysql_user}@#{opts.remote_mysql_host}..."
|
376
398
|
$stdout.flush
|
377
399
|
end
|
378
400
|
|
379
|
-
mysql_conn = "#{mysql_host}:#{remote_mysql_port || mysql_port}"
|
380
|
-
|
401
|
+
mysql_conn = "#{opts.mysql_host}:#{opts.remote_mysql_port || opts.mysql_port}"
|
381
402
|
begin
|
382
|
-
MySQLResults.conn = Mysql.new(mysql_host, mysql_user, mysql_password,
|
383
|
-
MySQLResults.database_name, mysql_port)
|
403
|
+
MySQLResults.conn = Mysql.new(opts.mysql_host, opts.mysql_user, opts.mysql_password,
|
404
|
+
MySQLResults.database_name, opts.mysql_port)
|
384
405
|
MySQLResults.conn.reconnect = true
|
385
406
|
puts 'connected' unless batch_input
|
386
407
|
rescue Mysql::Error => ex
|
@@ -389,20 +410,19 @@ rescue Mysql::Error => ex
|
|
389
410
|
else
|
390
411
|
$stderr.puts "failed to connect to #{mysql_conn} mysql server: #{ex.message}"
|
391
412
|
end
|
392
|
-
$stderr.puts ex.backtrace if verbose
|
413
|
+
$stderr.puts ex.backtrace if opts.verbose
|
393
414
|
exit 1
|
394
415
|
rescue NoMethodError
|
395
|
-
# this happens when mysql tries to read four bytes and assume it
|
396
|
-
#
|
397
|
-
#
|
398
|
-
#
|
399
|
-
# proxy
|
416
|
+
# this happens when mysql tries to read four bytes and assume it can index
|
417
|
+
# into them even when read returned nil...this happens because the connect
|
418
|
+
# succeeds due to the SSH forwarded port but then there isn't anybody
|
419
|
+
# connected on the remote side of the proxy
|
400
420
|
$stderr.puts "failed to connect to #{mysql_conn} mysql server"
|
401
|
-
$stderr.puts ex.backtrace if verbose
|
421
|
+
$stderr.puts ex.backtrace if opts.verbose
|
402
422
|
exit 1
|
403
423
|
rescue Exception => ex
|
404
424
|
$stderr.puts "failed to connect to #{mysql_conn} mysql server: #{ex.message} (#{ex.class})"
|
405
|
-
$stderr.puts ex.backtrace if verbose
|
425
|
+
$stderr.puts ex.backtrace if opts.verbose
|
406
426
|
exit 1
|
407
427
|
end
|
408
428
|
|
@@ -421,11 +441,11 @@ cmd_thread = Thread.new do
|
|
421
441
|
until me[:shutdown] do
|
422
442
|
default_displayer = :display_by_column
|
423
443
|
if batch_input
|
424
|
-
default_displayer = :display_by_batch if batch_output
|
444
|
+
default_displayer = :display_by_batch if opts.batch_output
|
425
445
|
me[:shutdown] = true # only run once
|
426
446
|
input = batch_input
|
427
447
|
else
|
428
|
-
puts '',"[#{mysql_user}@#{ssh_host||mysql_host}:#{MySQLResults.database_name}]"
|
448
|
+
puts '',"[#{opts.mysql_user}@#{opts.ssh_host||opts.mysql_host}:#{MySQLResults.database_name}]"
|
429
449
|
input = ''
|
430
450
|
prompt = eval_context.prompt || (bn + '> ')
|
431
451
|
loop do
|
@@ -465,8 +485,9 @@ cmd_thread = Thread.new do
|
|
465
485
|
end
|
466
486
|
|
467
487
|
# keep a secondary connection to allow us to kill off a running query
|
468
|
-
kill_conn = Mysql.new(mysql_host, mysql_user, mysql_password,
|
469
|
-
MySQLResults.database_name, mysql_port)
|
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)
|
470
491
|
|
471
492
|
Signal.trap('INT') do
|
472
493
|
# emulate MySQL's behavior
|
@@ -479,6 +500,7 @@ Signal.trap('INT') do
|
|
479
500
|
sleep(0.5)
|
480
501
|
end
|
481
502
|
MySQLResults.conn.select_db(MySQLResults.database_name)
|
503
|
+
eval_context.call_init_registrations
|
482
504
|
else
|
483
505
|
$stderr.puts 'Ctrl-C -- exit!'
|
484
506
|
cmd_thread[:shutdown] = true
|
@@ -488,7 +510,9 @@ Signal.trap('INT') do
|
|
488
510
|
end
|
489
511
|
|
490
512
|
Signal.trap('CHLD') do
|
491
|
-
|
513
|
+
unless opts.batch_output
|
514
|
+
$stderr.puts "SSH child (#{ssh_pid}) stopped--shutting down..."
|
515
|
+
end
|
492
516
|
if MySQLResults.conn && MySQLResults.conn.busy?
|
493
517
|
$stderr.puts 'Closing MySQL connection...'
|
494
518
|
safe_timeout(MySQLResults.conn, :close, 'MySQL')
|
@@ -502,7 +526,7 @@ end
|
|
502
526
|
begin
|
503
527
|
cmd_thread.join
|
504
528
|
rescue Exception => ex
|
505
|
-
$stderr.puts ex.message, ex.backtrace
|
529
|
+
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
506
530
|
end
|
507
531
|
|
508
532
|
unless MySQLResults.conn.nil?
|
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: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 7
|
10
|
+
version: 0.2.7
|
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-
|
18
|
+
date: 2012-02-13 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -120,18 +120,8 @@ extensions: []
|
|
120
120
|
extra_rdoc_files:
|
121
121
|
- README.rdoc
|
122
122
|
files:
|
123
|
-
- LICENSE
|
124
|
-
- README.rdoc
|
125
123
|
- bin/rsql
|
126
|
-
-
|
127
|
-
- extra/mysql-client-5.1.59-1.tgz
|
128
|
-
- lib/rsql.rb
|
129
|
-
- lib/rsql/commands.rb
|
130
|
-
- lib/rsql/eval_context.rb
|
131
|
-
- lib/rsql/mysql_results.rb
|
132
|
-
- test/test_commands.rb
|
133
|
-
- test/test_eval_context.rb
|
134
|
-
- test/test_mysql_results.rb
|
124
|
+
- README.rdoc
|
135
125
|
homepage: https://rubygems.org/gems/rsql
|
136
126
|
licenses: []
|
137
127
|
|
@@ -166,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
156
|
requirements: []
|
167
157
|
|
168
158
|
rubyforge_project:
|
169
|
-
rubygems_version: 1.8.
|
159
|
+
rubygems_version: 1.8.16
|
170
160
|
signing_key:
|
171
161
|
specification_version: 3
|
172
162
|
summary: Ruby based MySQL command line with recipes.
|
data/LICENSE
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
Copyright (C) 2011-2012 by brad+rsql@gigglewax.com
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
of this software and associated documentation files (the "Software"), to deal
|
5
|
-
in the Software without restriction, including without limitation the rights
|
6
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
copies of the Software, and to permit persons to whom the Software is
|
8
|
-
furnished to do so, subject to the following conditions:
|
9
|
-
|
10
|
-
The above copyright notice and this permission notice shall be included in
|
11
|
-
all copies or substantial portions of the Software.
|
12
|
-
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
THE SOFTWARE.
|
data/example.rsqlrc
DELETED
@@ -1,291 +0,0 @@
|
|
1
|
-
# -*- Mode: ruby -*-
|
2
|
-
|
3
|
-
# This file is meant to be a working illustration of how RSQL might be
|
4
|
-
# used and to show off various features of the application.
|
5
|
-
|
6
|
-
# All examples below will use this temporary table. You will need to
|
7
|
-
# "use" a database first before loading this file since it will need
|
8
|
-
# to create this temporary table.
|
9
|
-
#
|
10
|
-
@rsql_table = 'rsql_example'
|
11
|
-
|
12
|
-
# To use this file, change directory to the one containing this file,
|
13
|
-
# run rsql connecting to your MySQL server (run rsql with no arguments
|
14
|
-
# for usage).
|
15
|
-
#
|
16
|
-
# rsql> .load 'example.rsqlrc';
|
17
|
-
|
18
|
-
# After it's loaded try listing out all the registered recipes (along
|
19
|
-
# with parameter notes and descriptions).
|
20
|
-
#
|
21
|
-
# rsql> .list;
|
22
|
-
|
23
|
-
# If you make changes to the example to try out new things (and please
|
24
|
-
# do!), you can simply have the recipe file reloaded to have your
|
25
|
-
# changes pulled in immediately without exiting your session.
|
26
|
-
#
|
27
|
-
# rsql> .reload;
|
28
|
-
|
29
|
-
# Notice that any command issued starting with a period (.) results in
|
30
|
-
# evaluation of Ruby. Thus, any valid Ruby syntax is applicable
|
31
|
-
# following a period on a command.
|
32
|
-
|
33
|
-
################################################################################
|
34
|
-
|
35
|
-
# This type of registration is automatically invoked when this file is
|
36
|
-
# loaded. Often, this is useful to run set up routines like setting
|
37
|
-
# MySQL variables for different read levels (e.g. SET SESSION
|
38
|
-
# TRANSACTION ISOLATION LEVEL READ COMMITTED). Any number of these may
|
39
|
-
# be defined.
|
40
|
-
#
|
41
|
-
# Here we are merely setting up the example table.
|
42
|
-
#
|
43
|
-
register_init :setup_example, %q{
|
44
|
-
CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
45
|
-
name VARCHAR(100),
|
46
|
-
value INT(11),
|
47
|
-
stuff BLOB
|
48
|
-
)
|
49
|
-
}, :desc => 'Sets up example table for trying out RSQL.'
|
50
|
-
|
51
|
-
# This recipe is simply building up a string with a single variable
|
52
|
-
# interpolated into it (our table name). The string will then be used
|
53
|
-
# as if typed at the command line.
|
54
|
-
#
|
55
|
-
# rsql> .cleanup_example;
|
56
|
-
#
|
57
|
-
# In this case, we are simply dropping the table created by our
|
58
|
-
# initialization recipe. If you do this, you'll need to call the
|
59
|
-
# setup_example initialization recipe again before moving on.
|
60
|
-
#
|
61
|
-
# rsql> .setup_example;
|
62
|
-
#
|
63
|
-
register :cleanup_example, %q{
|
64
|
-
DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
65
|
-
}, :desc => 'Cleans up the example table.'
|
66
|
-
|
67
|
-
# This is an example of a recipe that utilizes a Ruby block for
|
68
|
-
# running code to generate the SQL we eventually return.
|
69
|
-
#
|
70
|
-
# Here we are just populating the table (if it isn't already).
|
71
|
-
#
|
72
|
-
# rsql> .fill_table;
|
73
|
-
#
|
74
|
-
# Notice the use of hexify and squeeze! methods available from
|
75
|
-
# EvalContext.
|
76
|
-
#
|
77
|
-
register :fill_table, :desc => 'Populate the example table.' do
|
78
|
-
sql = ''
|
79
|
-
9.times do |i|
|
80
|
-
sql << %{
|
81
|
-
INSERT IGNORE INTO #{@rsql_table}
|
82
|
-
SET name='fancy#{i}',
|
83
|
-
value=#{i**i},
|
84
|
-
stuff=#{hexify(rand((i+1)**100))};
|
85
|
-
}
|
86
|
-
end
|
87
|
-
# one more that isn't randomly generated so we can reference it
|
88
|
-
# later
|
89
|
-
sql << %{
|
90
|
-
INSERT IGNORE INTO #{@rsql_table}
|
91
|
-
SET name='fancy9',
|
92
|
-
value=#{9**9},
|
93
|
-
stuff=0x1234567891234567891234567890;
|
94
|
-
}
|
95
|
-
squeeze!(sql)
|
96
|
-
end
|
97
|
-
|
98
|
-
# A very common reason for recipes is simply to add parameters to be
|
99
|
-
# dropped in to our query. To facilitate this, simply declare one or
|
100
|
-
# more variables immediately following the name of the recipe. Then
|
101
|
-
# these values can be listed by embedded interpolation points into the
|
102
|
-
# string (just as you would with any Ruby string).
|
103
|
-
#
|
104
|
-
# This call will simply return results only for those bigger than some
|
105
|
-
# value passed in.
|
106
|
-
#
|
107
|
-
# rsql> .get_big_values 80000;
|
108
|
-
#
|
109
|
-
register :get_big_values, :val, %q{
|
110
|
-
SELECT name, value FROM #{@rsql_table} WHERE #{val} <= value
|
111
|
-
}, :desc => 'Get values bigger than the one provided as an argument.'
|
112
|
-
|
113
|
-
# Sometimes we make mistakes (never!). Normally, the command history
|
114
|
-
# kept in RSQL only stores the last thing entered at the prompt--not
|
115
|
-
# any query that the previous command may have generated and invoked.
|
116
|
-
# When writing a recipe that generates a query that has an error
|
117
|
-
# reported by MySQL, it is really handy to see the query.
|
118
|
-
#
|
119
|
-
# Here's an example of a recipe that will fail. Run it and then hit the
|
120
|
-
# "up arrow" key to see the previous command.
|
121
|
-
#
|
122
|
-
# rsql> .bad_query;
|
123
|
-
#
|
124
|
-
# So the command in our history is the recipe and not the query. To
|
125
|
-
# see the query the EvalContext has a recipe ready for us:
|
126
|
-
#
|
127
|
-
# rsql> .history;
|
128
|
-
#
|
129
|
-
register :bad_query, %q{
|
130
|
-
SELECT name, value FROM #{@rsql_table} WHERE valu < 10000
|
131
|
-
}, :desc => 'Make a query that will result in an error.'
|
132
|
-
|
133
|
-
# After you have a table with content in it, you can run queries
|
134
|
-
# against it and have the contents changed into something a little
|
135
|
-
# more meaningful. For example, what if the values in our table were
|
136
|
-
# bytes that we wanted to humanize? Try this command:
|
137
|
-
#
|
138
|
-
# rsql> select name, value from rsql_example ! value => humanize_bytes;
|
139
|
-
#
|
140
|
-
# The humanize_bytes method is a helper in the EvalContext
|
141
|
-
# class. There are several others available. Check out the rdoc for
|
142
|
-
# details.
|
143
|
-
#
|
144
|
-
# Additional mappings can be added, separated by commas.
|
145
|
-
#
|
146
|
-
# You can also declare these column mappings in your recipes, though
|
147
|
-
# the syntax is slightly different, using Ruby symbols.
|
148
|
-
#
|
149
|
-
# rsql> .show_values_as_bytes;
|
150
|
-
#
|
151
|
-
register :show_values_as_bytes, %q{
|
152
|
-
SELECT value FROM #{@rsql_table}
|
153
|
-
}, 'value' => :humanize_bytes,
|
154
|
-
:desc => 'Show values as humanized bytes.'
|
155
|
-
|
156
|
-
# It is even possible to make up your own column mapping helpers. Just
|
157
|
-
# create a Ruby method and reference it as a symbol mapped to whatever
|
158
|
-
# column the helper is expecting for content. The return of the helper
|
159
|
-
# will be replaced as the column entry's content. Your method is
|
160
|
-
# called once for each value in the column from the results.
|
161
|
-
#
|
162
|
-
# rsql> .show_pretty_names;
|
163
|
-
#
|
164
|
-
# Make sure if your method doesn't understand the content passed to it
|
165
|
-
# that it just reflects it back out so you don't lose data when
|
166
|
-
# printed.
|
167
|
-
#
|
168
|
-
def pretty_names(name)
|
169
|
-
if m = name.match(/^(\w+)(\d+)$/)
|
170
|
-
"#{m[1]} (#{m[2]})"
|
171
|
-
else
|
172
|
-
name
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
register :show_pretty_names, %q{
|
177
|
-
SELECT name FROM #{@rsql_table}
|
178
|
-
}, 'name' => :pretty_names,
|
179
|
-
:desc => 'Show names separated to be more readable.'
|
180
|
-
|
181
|
-
# It's also possible to work with the full set of query results in a
|
182
|
-
# recipe. This can be useful if there is some coordination necessary
|
183
|
-
# across multiple columns to result in some new kind of report. Much
|
184
|
-
# like a shell's ability to pipe output from one command to the next,
|
185
|
-
# RSQL takes a similar approach. Try this:
|
186
|
-
#
|
187
|
-
# rsql> select name, value from rsql_example | p @results;
|
188
|
-
#
|
189
|
-
# The EvalContext manages the results from a previous query in the
|
190
|
-
# @results member variable accessible by any Ruby recipe code. This is
|
191
|
-
# an instance of the MySQLResults class. Below we make use of the
|
192
|
-
# each_hash method to walk over all rows. There are other helpful
|
193
|
-
# routines available as well that are documented in rdoc.
|
194
|
-
#
|
195
|
-
# Here's an example that writes a simple report of the data we are
|
196
|
-
# working with. To try this out, enter the following at the prompt:
|
197
|
-
#
|
198
|
-
# rsql> select name, value from rsql_example | to_report;
|
199
|
-
#
|
200
|
-
register :to_report, :desc => 'Report on a count of small and big values.' do
|
201
|
-
small_cnt = 0
|
202
|
-
big_cnt = 0
|
203
|
-
@results.each_hash do |row|
|
204
|
-
if row['value'].to_i < 10000
|
205
|
-
small_cnt +=1
|
206
|
-
else
|
207
|
-
big_cnt += 1
|
208
|
-
end
|
209
|
-
end
|
210
|
-
puts "There are #{small_cnt} small values and #{big_cnt} big values."
|
211
|
-
end
|
212
|
-
|
213
|
-
# There may be other moments where it's necessary to take arguments,
|
214
|
-
# say if we want to process results and keep our data around in a
|
215
|
-
# file.
|
216
|
-
#
|
217
|
-
# rsql> select name, value from rsql_example | save_values 'myobj';
|
218
|
-
#
|
219
|
-
# After running this, a myobj.yml file should be created in the local
|
220
|
-
# directory containing all the content from the query. To accomplish
|
221
|
-
# this, the use of EvalContext's safe_save method is invoked which
|
222
|
-
# serializes our object so that we may later decided to run some post
|
223
|
-
# processing on the content.
|
224
|
-
#
|
225
|
-
# Inspect the YAML content written out:
|
226
|
-
#
|
227
|
-
# rsql> .puts IO.read('myobj.yml');
|
228
|
-
#
|
229
|
-
register :save_values, :desc => 'Save results from a query into a file.' do |fn|
|
230
|
-
myobj = {}
|
231
|
-
@results.each_hash do |row|
|
232
|
-
myobj[row['name']] = row['value']
|
233
|
-
end
|
234
|
-
safe_save(myobj, fn)
|
235
|
-
end
|
236
|
-
|
237
|
-
# Dealing with variable arguments is pretty straightforward as well,
|
238
|
-
# but with a little syntactic twist.
|
239
|
-
#
|
240
|
-
# rsql> .find_names 'fancy3', 'fancy8';
|
241
|
-
#
|
242
|
-
# Here we simply expand the arguments.
|
243
|
-
#
|
244
|
-
register :find_names, :'*names', %q{
|
245
|
-
SELECT name, value
|
246
|
-
FROM #{@rsql_table}
|
247
|
-
WHERE name IN (#{names.collect{|n| "'#{n}'"}.join(',')})
|
248
|
-
}, :desc => 'Find names from example table.'
|
249
|
-
|
250
|
-
# Sometimes it just isn't enough to be able to rely on generating SQL
|
251
|
-
# queries and piping into handlers. Sometimes we just need to roll up
|
252
|
-
# our sleeves and run queries directly so we can start processing
|
253
|
-
# results and dealing with presentation all on our own. That's where
|
254
|
-
# EvalContext's query helper comes in handy.
|
255
|
-
#
|
256
|
-
# The intention here is to just create a series of sentences out of
|
257
|
-
# two separate queries.
|
258
|
-
#
|
259
|
-
# rsql> .show_sentences;
|
260
|
-
#
|
261
|
-
register :show_sentences, :desc => 'Show results as sentences.' do
|
262
|
-
query("SELECT name FROM #{@rsql_table}").each_hash do |nrow|
|
263
|
-
name = nrow['name']
|
264
|
-
vals = query("SELECT value FROM #{@rsql_table} WHERE name='#{name}'")
|
265
|
-
puts "The #{name} has #{vals[0]['value']} fanciness levels."
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
# The MySQLResults class built in to RSQL handles binary content
|
270
|
-
# gracefully, automatically converting it to something a little nicer
|
271
|
-
# to our consoles than just dumping it. It converts it into a
|
272
|
-
# hexadecimal string.
|
273
|
-
#
|
274
|
-
# rsql> SELECT stuff FROM rsql_example;
|
275
|
-
#
|
276
|
-
# The default is to limit the hex strings to 32 "bytes" reported. This
|
277
|
-
# can be configured any time by setting the @hexstr_limit.
|
278
|
-
#
|
279
|
-
# RSQL makes querying for hex strings from within a recipe easy too.
|
280
|
-
#
|
281
|
-
# rsql> .find_stuff 0x1234567891234567891234567890;
|
282
|
-
#
|
283
|
-
register :find_stuff, :stuff, %q{
|
284
|
-
SELECT * FROM #{@rsql_table} WHERE stuff=#{hexify stuff}
|
285
|
-
}, :desc => 'Find some hex stuff.'
|
286
|
-
|
287
|
-
# There are many other things to try out left as an "exercise for the
|
288
|
-
# reader". Browsing the rdoc for EvalContext and MySQLResults would be
|
289
|
-
# an excellent start.
|
290
|
-
|
291
|
-
# vi: set filetype=ruby
|