rsql 0.2.6 → 0.2.7
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/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
|