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 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
- eval_context = EvalContext.new
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) = split_login(ARGV.delete_at(i))
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
- ssh_cfghash = Net::SSH::Config.load(default_config, ssh_host)
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) = split_login(ARGV.shift)
216
- mysql_password ||= get_password("#{mysql_user}@#{mysql_host} MySQL password: ")
217
- real_mysql_host = mysql_host
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
- opts = {:timeout => 15}
295
- opts[:config] = ssh_config if ssh_config
296
- if verbose
297
- opts[:verbose] = :debug
298
- puts "SSH options: #{opts.inspect}"
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
- opts[:password] = ssh_password if ssh_password
303
- ssh = Net::SSH.start(ssh_host, ssh_user, opts)
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
- $stderr.puts ex.message
348
- $stderr.puts ex.backtrace if verbose
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}@#{real_mysql_host}..."
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
- # can index into them even when read returned nil...this happens
397
- # because the connect succeeds due to the SSH forwarded port but
398
- # then there isn't anybody connected on the remote side of the
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
- $stderr.puts "SSH child (#{ssh_pid}) stopped--shutting down..."
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 6
10
- version: 0.2.6
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-01-16 00:00:00 Z
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
- - example.rsqlrc
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.15
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.
@@ -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