rsql 0.2.8 → 0.2.9
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/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
|