rsql 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +56 -58
- data/bin/rsql +13 -13
- data/example.rsqlrc +99 -104
- data/example.rsqlrc.rdoc +286 -0
- data/lib/rsql.rb +4 -1
- data/lib/rsql/eval_context.rb +52 -58
- data/lib/rsql/mysql_results.rb +1 -1
- metadata +10 -6
data/README.rdoc
CHANGED
@@ -4,19 +4,20 @@ Homepage:: https://rubygems.org/gems/rsql
|
|
4
4
|
|
5
5
|
== DESCRIPTION
|
6
6
|
|
7
|
-
This is an application to make working with a SQL command line more
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
This is an application to make working with a SQL command line more convenient
|
8
|
+
by allowing interaction with recipes and Ruby code in addition to embedding the
|
9
|
+
common operation of using a SSH connection to an intermediary host for access to
|
10
|
+
the SQL server.
|
11
11
|
|
12
12
|
=== Installation
|
13
13
|
|
14
14
|
gem install rsql
|
15
15
|
|
16
|
-
Alternatively, RSQL can be downloaded as a tar.gz or zip and run
|
17
|
-
|
18
|
-
|
19
|
-
unless the Net::SSH Ruby
|
16
|
+
Alternatively, RSQL can be downloaded as a tar.gz or zip and run directly from
|
17
|
+
within the unpacked source directory as long as the {MySQL Ruby
|
18
|
+
library}[https://rubygems.org/gems/mysqlplus] is available. SSH functionality
|
19
|
+
will be disabled unless the {Net::SSH Ruby
|
20
|
+
library}[https://rubygems.org/gems/net-ssh] is also installed and available.
|
20
21
|
|
21
22
|
== USAGE
|
22
23
|
|
@@ -33,68 +34,67 @@ RSQL is invoked from the command line using:
|
|
33
34
|
Display details on SSH connections and evaluation stack traces.
|
34
35
|
|
35
36
|
-rc _rcfile_::
|
36
|
-
Override loading the .rsqlrc file from the HOME directory for one in
|
37
|
-
|
37
|
+
Override loading the .rsqlrc file from the HOME directory for one in a
|
38
|
+
different location.
|
38
39
|
|
39
40
|
-maxrows _max_::
|
40
41
|
Override the maximum number of rows to process.
|
41
42
|
|
42
43
|
-batch _field_separator_::
|
43
|
-
Run in batch mode using the separator specified (e.g. a /t will
|
44
|
-
|
44
|
+
Run in batch mode using the separator specified (e.g. a /t will separate
|
45
|
+
fields with a tab character).
|
45
46
|
|
46
47
|
-ssh _ssh_host_::
|
47
48
|
Establish a SSH connection before connecting to the MySQL host.
|
48
49
|
|
49
50
|
-sshconfig _ssh_config_::
|
50
|
-
Use a specific SSH configuration file instead of the default files
|
51
|
-
|
51
|
+
Use a specific SSH configuration file instead of the default files loaded at
|
52
|
+
runtime by Net::SSH.
|
52
53
|
|
53
54
|
-e [_query_]::
|
54
|
-
Run a query from the command line (i.e. not interactive). If a
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
*must* be the last option specified.
|
55
|
+
Run a query from the command line (i.e. not interactive). If a _query_ is not
|
56
|
+
provided, STDIN will be read. Multiple commands can be issued in one set by
|
57
|
+
separation with semicolons just as if they had been provided at the RSQL
|
58
|
+
prompt interactively. This option *must* be the last option specified.
|
59
59
|
|
60
|
-
The _ssh_host_ and _mysql_host_ arguments may optionally include
|
61
|
-
|
60
|
+
The _ssh_host_ and _mysql_host_ arguments may optionally include _user_,
|
61
|
+
_password_, or _port_ values using the following syntax:
|
62
62
|
|
63
63
|
[<user>[:<password>]@]<host>[:<port>]
|
64
64
|
|
65
|
-
Once at the +rsql+ prompt, normal MySQL queries can be entered as
|
66
|
-
|
67
|
-
|
65
|
+
Once at the +rsql+ prompt, normal MySQL queries can be entered as expected,
|
66
|
+
ending each with a semicolon (;) for columnar output or \\G for line-by-line
|
67
|
+
output formatting.
|
68
68
|
|
69
|
-
Ruby commands will be evaluated for any content entered at the RSQL
|
70
|
-
|
69
|
+
Ruby commands will be evaluated for any content entered at the RSQL prompt
|
70
|
+
beginning with a period.
|
71
71
|
|
72
72
|
==== Command Line Examples
|
73
73
|
|
74
|
-
Connect as the "root" user to a MySQL server running on the local
|
75
|
-
|
76
|
-
|
74
|
+
Connect as the "root" user to a MySQL server running on the local host, with no
|
75
|
+
password (because there are no characters listed between the colon and the at
|
76
|
+
sign):
|
77
77
|
|
78
78
|
rsql root:@127.0.0.1
|
79
79
|
|
80
|
-
Connect as the "readonly" user to the "internal.database.acme.com"
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
the console (without echoing the characters typed):
|
80
|
+
Connect as the "readonly" user to the "internal.database.acme.com" host's MySQL
|
81
|
+
server after establishing a SSH tunnel to the "external.acme.com" gateway. In
|
82
|
+
this case, we are expecting that our SSH configuration is set up with the right
|
83
|
+
user name. Because we did not provide a password for MySQL, one will be obtained
|
84
|
+
directly from the console (without echoing the characters typed):
|
86
85
|
|
87
86
|
rsql -ssh external.acme.com readonly@internal.database.acme.com
|
88
87
|
|
89
88
|
== GETTING STARTED
|
90
89
|
|
91
|
-
This example walks through many features of RSQL including how to
|
92
|
-
|
90
|
+
This example walks through many features of RSQL including how to build up
|
91
|
+
recipes:
|
93
92
|
|
94
|
-
|
93
|
+
Try out the simple examples in rdoc-ref:example.rsqlrc.rdoc for a painless
|
94
|
+
introduciton for how to leverage RSQL.
|
95
95
|
|
96
|
-
The file is available as example.rsqlrc installed with the gem or
|
97
|
-
|
96
|
+
The file is available as example.rsqlrc installed with the gem or downloaded
|
97
|
+
with the source.
|
98
98
|
|
99
99
|
== LICENSE
|
100
100
|
|
@@ -102,21 +102,19 @@ RSQL is licensed under the MIT License:
|
|
102
102
|
|
103
103
|
Copyright (C) 2011-2012 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
104
104
|
|
105
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
IN
|
120
|
-
|
121
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
122
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
105
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
106
|
+
this software and associated documentation files (the "Software"), to deal in
|
107
|
+
the Software without restriction, including without limitation the rights to
|
108
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
109
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
110
|
+
subject to the following conditions:
|
111
|
+
|
112
|
+
The above copyright notice and this permission notice shall be included in all
|
113
|
+
copies or substantial portions of the Software.
|
114
|
+
|
115
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
116
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
117
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
118
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
119
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
120
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/rsql
CHANGED
@@ -179,14 +179,14 @@ end
|
|
179
179
|
|
180
180
|
if i = ARGV.index('-e')
|
181
181
|
ARGV.delete_at(i)
|
182
|
-
batch_input = ''
|
182
|
+
opts.batch_input = ''
|
183
183
|
ARGV.delete_if do |arg|
|
184
184
|
arg_i = ARGV.index(arg)
|
185
185
|
if i <= arg_i
|
186
|
-
batch_input << ' ' << arg
|
186
|
+
opts.batch_input << ' ' << arg
|
187
187
|
end
|
188
188
|
end
|
189
|
-
batch_input.strip!
|
189
|
+
opts.batch_input.strip!
|
190
190
|
end
|
191
191
|
|
192
192
|
show_usage = false
|
@@ -235,9 +235,9 @@ end
|
|
235
235
|
|
236
236
|
MySQLResults.database_name = ARGV.shift
|
237
237
|
|
238
|
-
if !$stdin.tty? && batch_input.nil?
|
238
|
+
if !$stdin.tty? && opts.batch_input.nil?
|
239
239
|
# accept commands from stdin
|
240
|
-
batch_input = $stdin.read.gsub(/\r?\n/,';')
|
240
|
+
opts.batch_input = $stdin.read.gsub(/\r?\n/,';')
|
241
241
|
end
|
242
242
|
|
243
243
|
# make sure we remove any duplicates when we add to the history to
|
@@ -277,7 +277,7 @@ if opts.ssh_host
|
|
277
277
|
|
278
278
|
password_retry_cnt = 0
|
279
279
|
|
280
|
-
unless batch_input
|
280
|
+
unless opts.batch_input
|
281
281
|
print "SSH #{opts.ssh_user}#{opts.ssh_user ? '@' : ''}#{opts.ssh_host}..."
|
282
282
|
$stdout.flush
|
283
283
|
end
|
@@ -297,7 +297,7 @@ if opts.ssh_host
|
|
297
297
|
ssh_enabled = false
|
298
298
|
Signal.trap('INT', 'IGNORE')
|
299
299
|
Signal.trap('TERM') do
|
300
|
-
$stderr.puts 'Closing SSH connection...' unless batch_input
|
300
|
+
$stderr.puts 'Closing SSH connection...' unless opts.batch_input
|
301
301
|
ssh_enabled = false
|
302
302
|
end
|
303
303
|
ssh_opts = {:timeout => 15}
|
@@ -313,7 +313,7 @@ if opts.ssh_host
|
|
313
313
|
ssh = Net::SSH.start(opts.ssh_host, opts.ssh_user, ssh_opts)
|
314
314
|
ssh_opts.delete(:password)
|
315
315
|
ssh_enabled = true
|
316
|
-
printf "connected (#{$$})..." unless batch_input
|
316
|
+
printf "connected (#{$$})..." unless opts.batch_input
|
317
317
|
$stdout.flush
|
318
318
|
rescue Net::SSH::AuthenticationFailed
|
319
319
|
if 2 < password_retry_cnt
|
@@ -337,7 +337,7 @@ if opts.ssh_host
|
|
337
337
|
ensure
|
338
338
|
if ssh_enabled
|
339
339
|
ssh.forward.local(opts.mysql_port, opts.mysql_host, opts.remote_mysql_port)
|
340
|
-
unless batch_input
|
340
|
+
unless opts.batch_input
|
341
341
|
puts(opts.verbose ? "ready (#{opts.mysql_port} => #{opts.remote_mysql_port})" : 'ready')
|
342
342
|
end
|
343
343
|
File.open(ipc_fn,'w'){|f| f.puts('ready')}
|
@@ -393,7 +393,7 @@ if opts.ssh_host
|
|
393
393
|
opts.mysql_host = '127.0.0.1'
|
394
394
|
end
|
395
395
|
|
396
|
-
unless batch_input
|
396
|
+
unless opts.batch_input
|
397
397
|
print "MySQL #{opts.mysql_user}@#{opts.remote_mysql_host}..."
|
398
398
|
$stdout.flush
|
399
399
|
end
|
@@ -403,7 +403,7 @@ begin
|
|
403
403
|
MySQLResults.conn = Mysql.new(opts.mysql_host, opts.mysql_user, opts.mysql_password,
|
404
404
|
MySQLResults.database_name, opts.mysql_port)
|
405
405
|
MySQLResults.conn.reconnect = true
|
406
|
-
puts 'connected' unless batch_input
|
406
|
+
puts 'connected' unless opts.batch_input
|
407
407
|
rescue Mysql::Error => ex
|
408
408
|
if ex.message.include?('Client does not support authentication')
|
409
409
|
$stderr.puts "failed to connect to #{mysql_conn} mysql server: unknown credentials?"
|
@@ -440,10 +440,10 @@ cmd_thread = Thread.new do
|
|
440
440
|
me[:shutdown] = false
|
441
441
|
until me[:shutdown] do
|
442
442
|
default_displayer = :display_by_column
|
443
|
-
if batch_input
|
443
|
+
if opts.batch_input
|
444
444
|
default_displayer = :display_by_batch if opts.batch_output
|
445
445
|
me[:shutdown] = true # only run once
|
446
|
-
input = batch_input
|
446
|
+
input = opts.batch_input
|
447
447
|
else
|
448
448
|
puts '',"[#{opts.mysql_user}@#{opts.ssh_host||opts.mysql_host}:#{MySQLResults.database_name}]"
|
449
449
|
input = ''
|
data/example.rsqlrc
CHANGED
@@ -1,42 +1,44 @@
|
|
1
1
|
# -*- Mode: ruby -*-
|
2
2
|
|
3
|
-
#
|
4
|
-
# used and to show off various features of the application.
|
3
|
+
# = Getting Starting with RSQL
|
5
4
|
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
5
|
+
# This file is meant to be a working illustration of how RSQL might be used and
|
6
|
+
# to show off various features of the application.
|
7
|
+
|
8
|
+
# All examples below will use the following temporary table. You will need to
|
9
|
+
# "use" a database first before loading this file since it will need to create
|
10
|
+
# this temporary table.
|
9
11
|
#
|
10
12
|
@rsql_table = 'rsql_example'
|
11
13
|
|
12
|
-
# To use this file, change directory to the one containing this file
|
13
|
-
#
|
14
|
-
# for
|
14
|
+
# To use this file, change directory to the one containing this file and run
|
15
|
+
# RSQL connecting to your MySQL server (run rsql with no arguments for
|
16
|
+
# usage--see rdoc-ref:README.rdoc for more details on command line parameters).
|
15
17
|
#
|
16
18
|
# rsql> .load 'example.rsqlrc';
|
17
19
|
|
18
|
-
# After it's loaded try listing out all the registered recipes (along
|
19
|
-
#
|
20
|
+
# After it's loaded try listing out all the registered recipes (along with
|
21
|
+
# parameter notes and descriptions).
|
20
22
|
#
|
21
23
|
# rsql> .list;
|
22
24
|
|
23
|
-
# If you make changes to the example to try out new things (and please
|
24
|
-
#
|
25
|
-
#
|
25
|
+
# If you make changes to the example to try out new things (and please do!), you
|
26
|
+
# can simply have the recipe file reloaded to have your changes pulled in
|
27
|
+
# immediately without exiting your session.
|
26
28
|
#
|
27
29
|
# rsql> .reload;
|
28
30
|
|
29
31
|
# Notice that any command issued starting with a period (.) results in
|
30
|
-
# evaluation of Ruby. Thus, any valid Ruby syntax is applicable
|
31
|
-
#
|
32
|
+
# evaluation of Ruby. Thus, any valid Ruby syntax is applicable following a
|
33
|
+
# period on a command.
|
32
34
|
|
33
35
|
################################################################################
|
34
36
|
|
35
|
-
#
|
36
|
-
# loaded. Often, this is useful to run set up routines
|
37
|
-
# MySQL variables for different read levels (e.g. SET
|
38
|
-
# TRANSACTION ISOLATION LEVEL READ COMMITTED). Any number of
|
39
|
-
# be defined.
|
37
|
+
# Use of RSQL::EvalContext#register_init allows a block to be automatically
|
38
|
+
# invoked when this file is loaded. Often, this is useful to run set up routines
|
39
|
+
# like setting MySQL variables for different read levels (e.g. <b><tt>SET
|
40
|
+
# SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED</tt></b>). Any number of
|
41
|
+
# these may be defined.
|
40
42
|
#
|
41
43
|
# Here we are merely setting up the example table.
|
42
44
|
#
|
@@ -48,15 +50,15 @@ CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
|
48
50
|
)
|
49
51
|
}, :desc => 'Sets up example table for trying out RSQL.'
|
50
52
|
|
51
|
-
# This recipe is
|
52
|
-
#
|
53
|
-
# as if typed at the command line.
|
53
|
+
# This next recipe is building up a string with a single variable interpolated
|
54
|
+
# into it (our table name) through RSQL::EvalContext#register. The string will
|
55
|
+
# then be used as if typed at the command line.
|
54
56
|
#
|
55
57
|
# rsql> .cleanup_example;
|
56
58
|
#
|
57
|
-
# In this case, we are simply dropping the table created by our
|
58
|
-
#
|
59
|
-
#
|
59
|
+
# In this case, we are simply dropping the table created by our initialization
|
60
|
+
# recipe. If you do this, you'll need to call the <b><tt>setup_example</tt></b>
|
61
|
+
# initialization recipe again before moving on.
|
60
62
|
#
|
61
63
|
# rsql> .setup_example;
|
62
64
|
#
|
@@ -64,15 +66,15 @@ register :cleanup_example, %q{
|
|
64
66
|
DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
65
67
|
}, :desc => 'Cleans up the example table.'
|
66
68
|
|
67
|
-
# This is an example of a recipe that utilizes a Ruby block for
|
68
|
-
#
|
69
|
+
# This is an example of a recipe that utilizes a Ruby block for running code to
|
70
|
+
# generate the SQL we eventually return.
|
69
71
|
#
|
70
72
|
# Here we are just populating the table (if it isn't already).
|
71
73
|
#
|
72
74
|
# rsql> .fill_table;
|
73
75
|
#
|
74
|
-
# Notice the use of hexify and squeeze!
|
75
|
-
#
|
76
|
+
# Notice the use of the RSQL::EvalContext#hexify and RSQL::EvalContext#squeeze!
|
77
|
+
# helper methods.
|
76
78
|
#
|
77
79
|
register :fill_table, :desc => 'Populate the example table.' do
|
78
80
|
sql = ''
|
@@ -84,9 +86,8 @@ INSERT IGNORE INTO #{@rsql_table}
|
|
84
86
|
stuff=#{hexify(rand((i+1)**100))};
|
85
87
|
}
|
86
88
|
end
|
87
|
-
# one more that isn't randomly generated so we can reference it
|
88
|
-
|
89
|
-
sql << %{
|
89
|
+
# one more that isn't randomly generated so we can reference it later
|
90
|
+
sql << %{
|
90
91
|
INSERT IGNORE INTO #{@rsql_table}
|
91
92
|
SET name='fancy9',
|
92
93
|
value=#{9**9},
|
@@ -95,14 +96,13 @@ INSERT IGNORE INTO #{@rsql_table}
|
|
95
96
|
squeeze!(sql)
|
96
97
|
end
|
97
98
|
|
98
|
-
# A very common reason for recipes is simply to add parameters to be
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
# string (just as you would with any Ruby string).
|
99
|
+
# A very common reason for recipes is simply to add parameters to be dropped in
|
100
|
+
# to our query. To facilitate this, declare one or more variables immediately
|
101
|
+
# following the name of the recipe. These values can be listed by embedded
|
102
|
+
# interpolation points into the string (just as you would with any Ruby string).
|
103
103
|
#
|
104
|
-
# This call will simply return results only for those bigger than some
|
105
|
-
#
|
104
|
+
# This call will simply return results only for those bigger than some value
|
105
|
+
# passed in.
|
106
106
|
#
|
107
107
|
# rsql> .get_big_values 80000;
|
108
108
|
#
|
@@ -110,19 +110,19 @@ register :get_big_values, :val, %q{
|
|
110
110
|
SELECT name, value FROM #{@rsql_table} WHERE #{val} <= value
|
111
111
|
}, :desc => 'Get values bigger than the one provided as an argument.'
|
112
112
|
|
113
|
-
# Sometimes we make mistakes (never!). Normally, the command history
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
113
|
+
# Sometimes we make mistakes (never!). Normally, the command history kept in
|
114
|
+
# RSQL only stores the last thing entered at the prompt--not any query that the
|
115
|
+
# previous command may have generated and invoked. When writing a recipe that
|
116
|
+
# generates a query that has an error reported by MySQL, it is really handy to
|
117
|
+
# see the generated SQL.
|
118
118
|
#
|
119
|
-
# Here's an example of a recipe that will fail. Run it and then hit the
|
120
|
-
#
|
119
|
+
# Here's an example of a recipe that will fail. Run it and then hit the "up
|
120
|
+
# arrow" key to see the previous command.
|
121
121
|
#
|
122
122
|
# rsql> .bad_query;
|
123
123
|
#
|
124
|
-
# So the command in our history is the recipe and not the query. To
|
125
|
-
#
|
124
|
+
# So the command in our history is the recipe and not the query. To see the
|
125
|
+
# query the RSQL::EvalContext#history has a helper method ready for us:
|
126
126
|
#
|
127
127
|
# rsql> .history;
|
128
128
|
#
|
@@ -130,21 +130,20 @@ register :bad_query, %q{
|
|
130
130
|
SELECT name, value FROM #{@rsql_table} WHERE valu < 10000
|
131
131
|
}, :desc => 'Make a query that will result in an error.'
|
132
132
|
|
133
|
-
# After you have a table with content in it, you can run queries
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
133
|
+
# After you have a table with content in it, you can run queries against it and
|
134
|
+
# have the contents changed into something a little more meaningful. For
|
135
|
+
# example, what if the values in our table were bytes that we wanted to
|
136
|
+
# humanize? Try this command:
|
137
137
|
#
|
138
138
|
# rsql> select name, value from rsql_example ! value => humanize_bytes;
|
139
139
|
#
|
140
|
-
# The humanize_bytes
|
141
|
-
#
|
142
|
-
# details.
|
140
|
+
# The RSQL::EvalContext#humanize_bytes is another helper method. There are
|
141
|
+
# several others available. Check out the RSQL::EvalContext class for details.
|
143
142
|
#
|
144
143
|
# Additional mappings can be added, separated by commas.
|
145
144
|
#
|
146
|
-
# You can also declare these column mappings in your recipes, though
|
147
|
-
#
|
145
|
+
# You can also declare these column mappings in your recipes, though the syntax
|
146
|
+
# is slightly different, using Ruby symbols.
|
148
147
|
#
|
149
148
|
# rsql> .show_values_as_bytes;
|
150
149
|
#
|
@@ -153,17 +152,16 @@ SELECT value FROM #{@rsql_table}
|
|
153
152
|
}, 'value' => :humanize_bytes,
|
154
153
|
:desc => 'Show values as humanized bytes.'
|
155
154
|
|
156
|
-
# It is even possible to make up your own column mapping helpers. Just
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
155
|
+
# It is even possible to make up your own column mapping helpers. Just create a
|
156
|
+
# Ruby method and reference it as a symbol mapped to whatever column the helper
|
157
|
+
# is expecting for content. The return of the helper will be replaced as the
|
158
|
+
# column entry's content. Your method is called once for each value in the
|
159
|
+
# column from the results.
|
161
160
|
#
|
162
161
|
# rsql> .show_pretty_names;
|
163
162
|
#
|
164
|
-
# Make sure if your method doesn't understand the content passed to it
|
165
|
-
#
|
166
|
-
# printed.
|
163
|
+
# Make sure if your method doesn't understand the content passed to it that it
|
164
|
+
# just reflects it back out so you don't lose data when printed.
|
167
165
|
#
|
168
166
|
def pretty_names(name)
|
169
167
|
if m = name.match(/^(\w+)(\d+)$/)
|
@@ -178,22 +176,22 @@ SELECT name FROM #{@rsql_table}
|
|
178
176
|
}, 'name' => :pretty_names,
|
179
177
|
:desc => 'Show names separated to be more readable.'
|
180
178
|
|
181
|
-
# It's also possible to work with the full set of query results in a
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
179
|
+
# It's also possible to work with the full set of query results in a recipe.
|
180
|
+
# This can be useful if there is some coordination necessary across multiple
|
181
|
+
# columns to result in some new kind of report. Much like a shell's ability to
|
182
|
+
# pipe output from one command to the next, RSQL takes a similar approach. Try
|
183
|
+
# this:
|
186
184
|
#
|
187
185
|
# rsql> select name, value from rsql_example | p @results;
|
188
186
|
#
|
189
|
-
# The EvalContext manages the results from a previous query in the
|
190
|
-
#
|
191
|
-
# an instance of the MySQLResults class. Below we make use
|
192
|
-
# each_hash method to walk over all rows. There are
|
193
|
-
# routines available
|
187
|
+
# The RSQL::EvalContext class manages the results from a previous query in the
|
188
|
+
# <b><tt>@results</tt></b> member variable accessible by any Ruby recipe
|
189
|
+
# code. This is an instance of the RSQL::MySQLResults class. Below we make use
|
190
|
+
# of the RSQL::MySQLResults#each_hash method to walk over all rows. There are
|
191
|
+
# other helpful routines available.
|
194
192
|
#
|
195
|
-
# Here's an example that writes a simple report of the data we are
|
196
|
-
#
|
193
|
+
# Here's an example that writes a simple report of the data we are working
|
194
|
+
# with. To try this out, enter the following at the prompt:
|
197
195
|
#
|
198
196
|
# rsql> select name, value from rsql_example | to_report;
|
199
197
|
#
|
@@ -210,19 +208,17 @@ register :to_report, :desc => 'Report on a count of small and big values.' do
|
|
210
208
|
puts "There are #{small_cnt} small values and #{big_cnt} big values."
|
211
209
|
end
|
212
210
|
|
213
|
-
# There may be other moments where it's necessary to take arguments,
|
214
|
-
#
|
215
|
-
# file.
|
211
|
+
# There may be other moments where it's necessary to take arguments, say if we
|
212
|
+
# want to process results and keep our data around in a file.
|
216
213
|
#
|
217
214
|
# rsql> select name, value from rsql_example | save_values 'myobj';
|
218
215
|
#
|
219
|
-
# After running this, a myobj.yml file should be created in the
|
220
|
-
# directory containing all the content from the query. To accomplish
|
221
|
-
#
|
222
|
-
#
|
223
|
-
# processing on the content.
|
216
|
+
# After running this, a <b><tt>myobj.yml</tt></b> file should be created in the
|
217
|
+
# local directory containing all the content from the query. To accomplish this,
|
218
|
+
# the RSQL::EvalContext#safe_save method is invoked which serializes our object
|
219
|
+
# so that we may later decided to run some post processing on the content.
|
224
220
|
#
|
225
|
-
# Inspect the YAML content written out:
|
221
|
+
# Inspect the YAML[http://www.yaml.org/] content written out:
|
226
222
|
#
|
227
223
|
# rsql> .puts IO.read('myobj.yml');
|
228
224
|
#
|
@@ -234,8 +230,8 @@ register :save_values, :desc => 'Save results from a query into a file.' do |fn|
|
|
234
230
|
safe_save(myobj, fn)
|
235
231
|
end
|
236
232
|
|
237
|
-
# Dealing with variable arguments is pretty straightforward as well,
|
238
|
-
#
|
233
|
+
# Dealing with variable arguments is pretty straightforward as well, but with a
|
234
|
+
# little syntactic twist.
|
239
235
|
#
|
240
236
|
# rsql> .find_names 'fancy3', 'fancy8';
|
241
237
|
#
|
@@ -247,14 +243,14 @@ SELECT name, value
|
|
247
243
|
WHERE name IN (#{names.collect{|n| "'#{n}'"}.join(',')})
|
248
244
|
}, :desc => 'Find names from example table.'
|
249
245
|
|
250
|
-
# Sometimes it just isn't enough to be able to rely on generating SQL
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
246
|
+
# Sometimes it just isn't enough to be able to rely on generating SQL queries
|
247
|
+
# and piping into handlers. Sometimes we just need to roll up our sleeves and
|
248
|
+
# run queries directly so we can start processing results and dealing with
|
249
|
+
# presentation all on our own. That's where the RSQL::EvalContext#query helper
|
250
|
+
# comes in handy.
|
255
251
|
#
|
256
|
-
# The intention here is to just create a series of sentences out of
|
257
|
-
#
|
252
|
+
# The intention here is to just create a series of sentences out of two separate
|
253
|
+
# queries.
|
258
254
|
#
|
259
255
|
# rsql> .show_sentences;
|
260
256
|
#
|
@@ -266,15 +262,14 @@ register :show_sentences, :desc => 'Show results as sentences.' do
|
|
266
262
|
end
|
267
263
|
end
|
268
264
|
|
269
|
-
# The MySQLResults class built in to RSQL handles binary content
|
270
|
-
# gracefully, automatically converting it to something a little nicer
|
271
|
-
#
|
272
|
-
# hexadecimal string.
|
265
|
+
# The RSQL::MySQLResults class built in to RSQL handles binary content
|
266
|
+
# gracefully, automatically converting it to something a little nicer to our
|
267
|
+
# consoles than just dumping it. It converts it into a hexadecimal string.
|
273
268
|
#
|
274
269
|
# rsql> SELECT stuff FROM rsql_example;
|
275
270
|
#
|
276
|
-
# The default is to limit the hex strings to 32 "bytes" reported. This
|
277
|
-
#
|
271
|
+
# The default is to limit the hex strings to 32 "bytes" reported. This can be
|
272
|
+
# configured any time by setting the <b><tt>@hexstr_limit</tt></b>.
|
278
273
|
#
|
279
274
|
# RSQL makes querying for hex strings from within a recipe easy too.
|
280
275
|
#
|
@@ -285,7 +280,7 @@ SELECT * FROM #{@rsql_table} WHERE stuff=#{hexify stuff}
|
|
285
280
|
}, :desc => 'Find some hex stuff.'
|
286
281
|
|
287
282
|
# There are many other things to try out left as an "exercise for the
|
288
|
-
# reader". Browsing the
|
289
|
-
# an excellent start.
|
283
|
+
# reader". Browsing the RSQL::EvalContext and RSQL::MySQLResults classes would
|
284
|
+
# be an excellent start.
|
290
285
|
|
291
286
|
# vi: set filetype=ruby
|
data/example.rsqlrc.rdoc
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
= Getting Starting with RSQL
|
4
|
+
|
5
|
+
This file is meant to be a working illustration of how RSQL might be used and
|
6
|
+
to show off various features of the application.
|
7
|
+
|
8
|
+
All examples below will use the following temporary table. You will need to
|
9
|
+
"use" a database first before loading this file since it will need to create
|
10
|
+
this temporary table.
|
11
|
+
|
12
|
+
@rsql_table = 'rsql_example'
|
13
|
+
|
14
|
+
To use this file, change directory to the one containing this file and run
|
15
|
+
RSQL connecting to your MySQL server (run rsql with no arguments for
|
16
|
+
usage--see rdoc-ref:README.rdoc for more details on command line parameters).
|
17
|
+
|
18
|
+
<tt>rsql> .load 'example.rsqlrc';</tt>
|
19
|
+
|
20
|
+
After it's loaded try listing out all the registered recipes (along with
|
21
|
+
parameter notes and descriptions).
|
22
|
+
|
23
|
+
<tt>rsql> .list;</tt>
|
24
|
+
|
25
|
+
If you make changes to the example to try out new things (and please do!), you
|
26
|
+
can simply have the recipe file reloaded to have your changes pulled in
|
27
|
+
immediately without exiting your session.
|
28
|
+
|
29
|
+
<tt>rsql> .reload;</tt>
|
30
|
+
|
31
|
+
Notice that any command issued starting with a period (.) results in
|
32
|
+
evaluation of Ruby. Thus, any valid Ruby syntax is applicable following a
|
33
|
+
period on a command.
|
34
|
+
|
35
|
+
---
|
36
|
+
|
37
|
+
Use of RSQL::EvalContext#register_init allows a block to be automatically
|
38
|
+
invoked when this file is loaded. Often, this is useful to run set up routines
|
39
|
+
like setting MySQL variables for different read levels (e.g. <b><tt>SET
|
40
|
+
SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED</tt></b>). Any number of
|
41
|
+
these may be defined.
|
42
|
+
|
43
|
+
Here we are merely setting up the example table.
|
44
|
+
|
45
|
+
register_init :setup_example, %q{
|
46
|
+
CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
47
|
+
name VARCHAR(100),
|
48
|
+
value INT(11),
|
49
|
+
stuff BLOB
|
50
|
+
)
|
51
|
+
}, :desc => 'Sets up example table for trying out RSQL.'
|
52
|
+
|
53
|
+
This next recipe is building up a string with a single variable interpolated
|
54
|
+
into it (our table name) through RSQL::EvalContext#register. The string will
|
55
|
+
then be used as if typed at the command line.
|
56
|
+
|
57
|
+
<tt>rsql> .cleanup_example;</tt>
|
58
|
+
|
59
|
+
In this case, we are simply dropping the table created by our initialization
|
60
|
+
recipe. If you do this, you'll need to call the <b><tt>setup_example</tt></b>
|
61
|
+
initialization recipe again before moving on.
|
62
|
+
|
63
|
+
<tt>rsql> .setup_example;</tt>
|
64
|
+
|
65
|
+
register :cleanup_example, %q{
|
66
|
+
DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
67
|
+
}, :desc => 'Cleans up the example table.'
|
68
|
+
|
69
|
+
This is an example of a recipe that utilizes a Ruby block for running code to
|
70
|
+
generate the SQL we eventually return.
|
71
|
+
|
72
|
+
Here we are just populating the table (if it isn't already).
|
73
|
+
|
74
|
+
<tt>rsql> .fill_table;</tt>
|
75
|
+
|
76
|
+
Notice the use of the RSQL::EvalContext#hexify and RSQL::EvalContext#squeeze!
|
77
|
+
helper methods.
|
78
|
+
|
79
|
+
register :fill_table, :desc => 'Populate the example table.' do
|
80
|
+
sql = ''
|
81
|
+
9.times do |i|
|
82
|
+
sql << %{
|
83
|
+
INSERT IGNORE INTO #{@rsql_table}
|
84
|
+
SET name='fancy#{i}',
|
85
|
+
value=#{i**i},
|
86
|
+
stuff=#{hexify(rand((i+1)**100))};
|
87
|
+
}
|
88
|
+
end
|
89
|
+
# one more that isn't randomly generated so we can reference it later
|
90
|
+
sql << %{
|
91
|
+
INSERT IGNORE INTO #{@rsql_table}
|
92
|
+
SET name='fancy9',
|
93
|
+
value=#{9**9},
|
94
|
+
stuff=0x1234567891234567891234567890;
|
95
|
+
}
|
96
|
+
squeeze!(sql)
|
97
|
+
end
|
98
|
+
|
99
|
+
A very common reason for recipes is simply to add parameters to be dropped in
|
100
|
+
to our query. To facilitate this, declare one or more variables immediately
|
101
|
+
following the name of the recipe. These values can be listed by embedded
|
102
|
+
interpolation points into the string (just as you would with any Ruby string).
|
103
|
+
|
104
|
+
This call will simply return results only for those bigger than some value
|
105
|
+
passed in.
|
106
|
+
|
107
|
+
<tt>rsql> .get_big_values 80000;</tt>
|
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 kept in
|
114
|
+
RSQL only stores the last thing entered at the prompt--not any query that the
|
115
|
+
previous command may have generated and invoked. When writing a recipe that
|
116
|
+
generates a query that has an error reported by MySQL, it is really handy to
|
117
|
+
see the generated SQL.
|
118
|
+
|
119
|
+
Here's an example of a recipe that will fail. Run it and then hit the "up
|
120
|
+
arrow" key to see the previous command.
|
121
|
+
|
122
|
+
<tt>rsql> .bad_query;</tt>
|
123
|
+
|
124
|
+
So the command in our history is the recipe and not the query. To see the
|
125
|
+
query the RSQL::EvalContext#history has a helper method ready for us:
|
126
|
+
|
127
|
+
<tt>rsql> .history;</tt>
|
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 against it and
|
134
|
+
have the contents changed into something a little more meaningful. For
|
135
|
+
example, what if the values in our table were bytes that we wanted to
|
136
|
+
humanize? Try this command:
|
137
|
+
|
138
|
+
<tt>rsql> select name, value from rsql_example ! value => humanize_bytes;</tt>
|
139
|
+
|
140
|
+
The RSQL::EvalContext#humanize_bytes is another helper method. There are
|
141
|
+
several others available. Check out the RSQL::EvalContext class for details.
|
142
|
+
|
143
|
+
Additional mappings can be added, separated by commas.
|
144
|
+
|
145
|
+
You can also declare these column mappings in your recipes, though the syntax
|
146
|
+
is slightly different, using Ruby symbols.
|
147
|
+
|
148
|
+
<tt>rsql> .show_values_as_bytes;</tt>
|
149
|
+
|
150
|
+
register :show_values_as_bytes, %q{
|
151
|
+
SELECT value FROM #{@rsql_table}
|
152
|
+
}, 'value' => :humanize_bytes,
|
153
|
+
:desc => 'Show values as humanized bytes.'
|
154
|
+
|
155
|
+
It is even possible to make up your own column mapping helpers. Just create a
|
156
|
+
Ruby method and reference it as a symbol mapped to whatever column the helper
|
157
|
+
is expecting for content. The return of the helper will be replaced as the
|
158
|
+
column entry's content. Your method is called once for each value in the
|
159
|
+
column from the results.
|
160
|
+
|
161
|
+
<tt>rsql> .show_pretty_names;</tt>
|
162
|
+
|
163
|
+
Make sure if your method doesn't understand the content passed to it that it
|
164
|
+
just reflects it back out so you don't lose data when printed.
|
165
|
+
|
166
|
+
def pretty_names(name)
|
167
|
+
if m = name.match(/^(\w+)(\d+)$/)
|
168
|
+
"#{m[1]} (#{m[2]})"
|
169
|
+
else
|
170
|
+
name
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
register :show_pretty_names, %q{
|
175
|
+
SELECT name FROM #{@rsql_table}
|
176
|
+
}, 'name' => :pretty_names,
|
177
|
+
:desc => 'Show names separated to be more readable.'
|
178
|
+
|
179
|
+
It's also possible to work with the full set of query results in a recipe.
|
180
|
+
This can be useful if there is some coordination necessary across multiple
|
181
|
+
columns to result in some new kind of report. Much like a shell's ability to
|
182
|
+
pipe output from one command to the next, RSQL takes a similar approach. Try
|
183
|
+
this:
|
184
|
+
|
185
|
+
<tt>rsql> select name, value from rsql_example | p @results;</tt>
|
186
|
+
|
187
|
+
The RSQL::EvalContext class manages the results from a previous query in the
|
188
|
+
<b><tt>@results</tt></b> member variable accessible by any Ruby recipe
|
189
|
+
code. This is an instance of the RSQL::MySQLResults class. Below we make use
|
190
|
+
of the RSQL::MySQLResults#each_hash method to walk over all rows. There are
|
191
|
+
other helpful routines available.
|
192
|
+
|
193
|
+
Here's an example that writes a simple report of the data we are working
|
194
|
+
with. To try this out, enter the following at the prompt:
|
195
|
+
|
196
|
+
<tt>rsql> select name, value from rsql_example | to_report;</tt>
|
197
|
+
|
198
|
+
register :to_report, :desc => 'Report on a count of small and big values.' do
|
199
|
+
small_cnt = 0
|
200
|
+
big_cnt = 0
|
201
|
+
@results.each_hash do |row|
|
202
|
+
if row['value'].to_i < 10000
|
203
|
+
small_cnt +=1
|
204
|
+
else
|
205
|
+
big_cnt += 1
|
206
|
+
end
|
207
|
+
end
|
208
|
+
puts "There are #{small_cnt} small values and #{big_cnt} big values."
|
209
|
+
end
|
210
|
+
|
211
|
+
There may be other moments where it's necessary to take arguments, say if we
|
212
|
+
want to process results and keep our data around in a file.
|
213
|
+
|
214
|
+
<tt>rsql> select name, value from rsql_example | save_values 'myobj';</tt>
|
215
|
+
|
216
|
+
After running this, a <b><tt>myobj.yml</tt></b> file should be created in the
|
217
|
+
local directory containing all the content from the query. To accomplish this,
|
218
|
+
the RSQL::EvalContext#safe_save method is invoked which serializes our object
|
219
|
+
so that we may later decided to run some post processing on the content.
|
220
|
+
|
221
|
+
Inspect the YAML[http://www.yaml.org/] content written out:
|
222
|
+
|
223
|
+
<tt>rsql> .puts IO.read('myobj.yml');</tt>
|
224
|
+
|
225
|
+
register :save_values, :desc => 'Save results from a query into a file.' do |fn|
|
226
|
+
myobj = {}
|
227
|
+
@results.each_hash do |row|
|
228
|
+
myobj[row['name']] = row['value']
|
229
|
+
end
|
230
|
+
safe_save(myobj, fn)
|
231
|
+
end
|
232
|
+
|
233
|
+
Dealing with variable arguments is pretty straightforward as well, but with a
|
234
|
+
little syntactic twist.
|
235
|
+
|
236
|
+
<tt>rsql> .find_names 'fancy3', 'fancy8';</tt>
|
237
|
+
|
238
|
+
Here we simply expand the arguments.
|
239
|
+
|
240
|
+
register :find_names, :'*names', %q{
|
241
|
+
SELECT name, value
|
242
|
+
FROM #{@rsql_table}
|
243
|
+
WHERE name IN (#{names.collect{|n| "'#{n}'"}.join(',')})
|
244
|
+
}, :desc => 'Find names from example table.'
|
245
|
+
|
246
|
+
Sometimes it just isn't enough to be able to rely on generating SQL queries
|
247
|
+
and piping into handlers. Sometimes we just need to roll up our sleeves and
|
248
|
+
run queries directly so we can start processing results and dealing with
|
249
|
+
presentation all on our own. That's where the RSQL::EvalContext#query helper
|
250
|
+
comes in handy.
|
251
|
+
|
252
|
+
The intention here is to just create a series of sentences out of two separate
|
253
|
+
queries.
|
254
|
+
|
255
|
+
<tt>rsql> .show_sentences;</tt>
|
256
|
+
|
257
|
+
register :show_sentences, :desc => 'Show results as sentences.' do
|
258
|
+
query("SELECT name FROM #{@rsql_table}").each_hash do |nrow|
|
259
|
+
name = nrow['name']
|
260
|
+
vals = query("SELECT value FROM #{@rsql_table} WHERE name='#{name}'")
|
261
|
+
puts "The #{name} has #{vals[0]['value']} fanciness levels."
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
The RSQL::MySQLResults class built in to RSQL handles binary content
|
266
|
+
gracefully, automatically converting it to something a little nicer to our
|
267
|
+
consoles than just dumping it. It converts it into a hexadecimal string.
|
268
|
+
|
269
|
+
<tt>rsql> SELECT stuff FROM rsql_example;</tt>
|
270
|
+
|
271
|
+
The default is to limit the hex strings to 32 "bytes" reported. This can be
|
272
|
+
configured any time by setting the <b><tt>@hexstr_limit</tt></b>.
|
273
|
+
|
274
|
+
RSQL makes querying for hex strings from within a recipe easy too.
|
275
|
+
|
276
|
+
<tt>rsql> .find_stuff 0x1234567891234567891234567890;</tt>
|
277
|
+
|
278
|
+
register :find_stuff, :stuff, %q{
|
279
|
+
SELECT * FROM #{@rsql_table} WHERE stuff=#{hexify stuff}
|
280
|
+
}, :desc => 'Find some hex stuff.'
|
281
|
+
|
282
|
+
There are many other things to try out left as an "exercise for the
|
283
|
+
reader". Browsing the RSQL::EvalContext and RSQL::MySQLResults classes would
|
284
|
+
be an excellent start.
|
285
|
+
|
286
|
+
|
data/lib/rsql.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# A module encapsulating classes to manage MySQLResults and process
|
2
2
|
# Commands using an EvalContext for handling recipes.
|
3
3
|
#
|
4
|
+
# See the rdoc-ref:example.rsqlrc.rdoc file for a simple tutorial and usage
|
5
|
+
# information.
|
6
|
+
#
|
4
7
|
module RSQL
|
5
|
-
VERSION = '0.2.
|
8
|
+
VERSION = '0.2.9'
|
6
9
|
|
7
10
|
require 'rsql/mysql_results'
|
8
11
|
require 'rsql/eval_context'
|
data/lib/rsql/eval_context.rb
CHANGED
@@ -63,7 +63,7 @@ module RSQL
|
|
63
63
|
'Show short syntax help.'),
|
64
64
|
:grep => Registration.new('grep', [], {},
|
65
65
|
method(:grep),
|
66
|
-
'grep',
|
66
|
+
'grep(string_or_regexp, *options)',
|
67
67
|
'Show results when regular expression matches any part of the content.'),
|
68
68
|
:reload => Registration.new('reload', [], {},
|
69
69
|
method(:reload),
|
@@ -71,7 +71,7 @@ module RSQL
|
|
71
71
|
'Reload the rsqlrc file.'),
|
72
72
|
:desc => Registration.new('desc', [], {},
|
73
73
|
method(:desc),
|
74
|
-
'desc',
|
74
|
+
'desc(name)',
|
75
75
|
'Describe the content of a recipe.'),
|
76
76
|
:history => Registration.new('history', [], {},
|
77
77
|
method(:history),
|
@@ -79,7 +79,7 @@ module RSQL
|
|
79
79
|
'Print recent queries made (request a count or use :all for entire list).'),
|
80
80
|
:set_max_rows => Registration.new('set_max_rows', [], {},
|
81
81
|
Proc.new{|r| MySQLResults.max_rows = r},
|
82
|
-
'set_max_rows',
|
82
|
+
'set_max_rows(max)',
|
83
83
|
'Set the maximum number of rows to process.'),
|
84
84
|
:max_rows => Registration.new('max_rows', [], {},
|
85
85
|
Proc.new{MySQLResults.max_rows},
|
@@ -334,59 +334,62 @@ module RSQL
|
|
334
334
|
return nil
|
335
335
|
end
|
336
336
|
|
337
|
+
# Used by params() and desc() to find where a block begins.
|
338
|
+
#
|
339
|
+
def locate_block_start(name, io, lineno, ending=nil, source=nil)
|
340
|
+
i = 0
|
341
|
+
param_line = ''
|
342
|
+
params = nil
|
343
|
+
|
344
|
+
while line = io.gets
|
345
|
+
i += 1
|
346
|
+
next if i < lineno
|
347
|
+
source << line if source
|
348
|
+
|
349
|
+
# give up if no start found within 20 lines
|
350
|
+
break if lineno + 20 < i
|
351
|
+
if m = line.match(/(\{|do\b)(.*)$/)
|
352
|
+
if ending
|
353
|
+
ending << (m[1] == '{' ? '\}' : 'end')
|
354
|
+
end
|
355
|
+
# adjust line to be the remainder after the start
|
356
|
+
param_line = m[2]
|
357
|
+
break
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
if m = param_line.match(/^\s*\|([^\|]*)\|/)
|
362
|
+
return "(#{m[1]})"
|
363
|
+
else
|
364
|
+
return nil
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
337
368
|
# Attempt to locate the parameters of a given block by
|
338
369
|
# searching its source.
|
339
370
|
#
|
340
371
|
def params(name, block)
|
341
|
-
params =
|
372
|
+
params = nil
|
342
373
|
|
343
|
-
if block.arity != 0 && block.
|
344
|
-
block.inspect.match(/@(.+):(\d+)>$/)
|
374
|
+
if block.arity != 0 && block.inspect.match(/@(.+):(\d+)>$/)
|
345
375
|
fn = $1
|
346
376
|
lineno = $2.to_i
|
347
377
|
|
348
378
|
if fn == '(eval)'
|
349
379
|
$stderr.puts "refusing to search an eval block for :#{name}"
|
350
|
-
return
|
380
|
+
return ''
|
351
381
|
end
|
352
382
|
|
353
383
|
File.open(fn) do |f|
|
354
|
-
|
355
|
-
found = false
|
356
|
-
while line = f.gets
|
357
|
-
i += 1
|
358
|
-
next if i < lineno
|
359
|
-
|
360
|
-
unless found
|
361
|
-
# give up if no start found within 20
|
362
|
-
# lines
|
363
|
-
break if lineno + 20 < i
|
364
|
-
if m = line.match(/(\{|do\b)(.*)$/)
|
365
|
-
# adjust line to be the remainder
|
366
|
-
# after the start
|
367
|
-
line = m[2]
|
368
|
-
found = true
|
369
|
-
else
|
370
|
-
next
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
if m = line.match(/^\s*\|([^\|]*)\|/)
|
375
|
-
params = "(#{m[1]})"
|
376
|
-
break
|
377
|
-
end
|
378
|
-
|
379
|
-
# if the params aren't here then we'd
|
380
|
-
# better only have whitespace otherwise
|
381
|
-
# this block doesn't have params...even
|
382
|
-
# though arity says it should
|
383
|
-
next if line.match(/^\s*$/)
|
384
|
-
$stderr.puts "unable to locate params for :#{name}"
|
385
|
-
break
|
386
|
-
end
|
384
|
+
params = locate_block_start(name, f, lineno)
|
387
385
|
end
|
388
386
|
end
|
389
387
|
|
388
|
+
if params.nil?
|
389
|
+
$stderr.puts "unable to locate params for :#{name}" if @verbose
|
390
|
+
return ''
|
391
|
+
end
|
392
|
+
|
390
393
|
return params
|
391
394
|
end
|
392
395
|
|
@@ -428,23 +431,13 @@ module RSQL
|
|
428
431
|
|
429
432
|
File.open(fn) do |f|
|
430
433
|
source = ''
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
+
ending = ''
|
435
|
+
|
436
|
+
locate_block_start(sym, f, lineno, ending, source)
|
437
|
+
break if ending.empty?
|
434
438
|
|
435
439
|
while line = f.gets
|
436
|
-
i += 1
|
437
|
-
next unless ending || i == lineno
|
438
440
|
source << line
|
439
|
-
unless ending
|
440
|
-
unless m = line.match(/\{|do\b/)
|
441
|
-
$stderr.puts "unable to locate block beginning at #{fn}:#{lineno}"
|
442
|
-
return
|
443
|
-
end
|
444
|
-
ending = m[0] == '{' ? '\}' : 'end'
|
445
|
-
next
|
446
|
-
end
|
447
|
-
|
448
441
|
if m = line.match(/^#{ending}/)
|
449
442
|
found = true
|
450
443
|
break
|
@@ -503,7 +496,8 @@ EOF
|
|
503
496
|
end
|
504
497
|
|
505
498
|
# Provide a helper utility in the event a registered method would
|
506
|
-
# like to make its own queries.
|
499
|
+
# like to make its own queries. See MySQLResults.query for more
|
500
|
+
# details regarding the other arguments available.
|
507
501
|
#
|
508
502
|
def query(content, *args) # :doc:
|
509
503
|
MySQLResults.query(content, self, *args)
|
@@ -512,17 +506,17 @@ EOF
|
|
512
506
|
# Show the most recent queries made to the MySQL server in this
|
513
507
|
# session. Default is to show the last one.
|
514
508
|
#
|
515
|
-
def history(cnt=1)
|
509
|
+
def history(cnt=1) # :doc:
|
516
510
|
if h = MySQLResults.history(cnt)
|
517
511
|
h.each{|q| puts '', q}
|
518
512
|
end
|
519
513
|
nil
|
520
514
|
end
|
521
515
|
|
522
|
-
# Call MySQLResults
|
516
|
+
# Call MySQLResults#grep to remove (or show) only those lines that
|
523
517
|
# have content matching the patterrn.
|
524
518
|
#
|
525
|
-
def grep(*args)
|
519
|
+
def grep(*args) # :doc:
|
526
520
|
if @results.grep(*args)
|
527
521
|
@results
|
528
522
|
else
|
data/lib/rsql/mysql_results.rb
CHANGED
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: 5
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 9
|
10
|
+
version: 0.2.9
|
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-02-
|
18
|
+
date: 2012-02-15 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -116,6 +116,7 @@ extensions: []
|
|
116
116
|
extra_rdoc_files:
|
117
117
|
- README.rdoc
|
118
118
|
- LICENSE
|
119
|
+
- example.rsqlrc.rdoc
|
119
120
|
files:
|
120
121
|
- LICENSE
|
121
122
|
- README.rdoc
|
@@ -129,6 +130,7 @@ files:
|
|
129
130
|
- test/test_commands.rb
|
130
131
|
- test/test_eval_context.rb
|
131
132
|
- test/test_mysql_results.rb
|
133
|
+
- example.rsqlrc.rdoc
|
132
134
|
homepage: https://rubygems.org/gems/rsql
|
133
135
|
licenses: []
|
134
136
|
|
@@ -167,5 +169,7 @@ rubygems_version: 1.8.16
|
|
167
169
|
signing_key:
|
168
170
|
specification_version: 3
|
169
171
|
summary: Ruby-based MySQL command line with recipes.
|
170
|
-
test_files:
|
171
|
-
|
172
|
+
test_files:
|
173
|
+
- test/test_commands.rb
|
174
|
+
- test/test_eval_context.rb
|
175
|
+
- test/test_mysql_results.rb
|