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 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