rsql 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +86 -0
- data/TODO +4 -3
- data/bin/rsql +0 -5
- data/example.rsqlrc +156 -25
- data/lib/rsql.rb +4 -1
- data/lib/rsql/commands.rb +5 -2
- data/lib/rsql/eval_context.rb +37 -36
- data/lib/rsql/mysql_results.rb +29 -21
- metadata +13 -10
- data/README.txt +0 -130
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= RSQL
|
2
|
+
|
3
|
+
Homepage:: https://github.com/bradrf/rsql
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
This is an application to make working with a SQL command line more
|
8
|
+
convenient by allowing interaction with recipes and Ruby code in
|
9
|
+
addition to embedding the common operation of using a SSH connection
|
10
|
+
to an intermediary host for access to the SQL server.
|
11
|
+
|
12
|
+
=== Installation
|
13
|
+
|
14
|
+
gem install rsql
|
15
|
+
|
16
|
+
== USAGE
|
17
|
+
|
18
|
+
RSQL is invoked from the comamnd line using:
|
19
|
+
|
20
|
+
rsql [<options>] <mysql_host> [<database>] [-e [<query>]]
|
21
|
+
|
22
|
+
=== Options
|
23
|
+
|
24
|
+
-version::
|
25
|
+
Display the version of RSQL that is installed.
|
26
|
+
|
27
|
+
-rc _rcfile_::
|
28
|
+
Override loading the .rsqlrc file from the HOME directory for one in
|
29
|
+
a different location.
|
30
|
+
|
31
|
+
-maxrows _max_::
|
32
|
+
Override the maximum number of rows to process.
|
33
|
+
|
34
|
+
-batch _field_separator_::
|
35
|
+
Run in batch mode using the separator specifed (e.g. a /t will
|
36
|
+
separate fields with a tab character).
|
37
|
+
|
38
|
+
-ssh _ssh_host_::
|
39
|
+
Establish an SSH connection before connecting to the MySQL host.
|
40
|
+
|
41
|
+
-e [_query_]::
|
42
|
+
Run a query from the command line (i.e. not interactive). If a
|
43
|
+
_query_ is not provided, STDIN will be read. Multiple commands can
|
44
|
+
be issued in one set by separation with semicolons just as if they
|
45
|
+
had been provided at the RSQL prompt interactively. This option
|
46
|
+
*must* be the last option specified.
|
47
|
+
|
48
|
+
The _ssh_host_ and _mysql_host_ values may also provide _user_ and
|
49
|
+
_password_ values using the following syntax:
|
50
|
+
|
51
|
+
[<user>[:<password>]@]<host>
|
52
|
+
|
53
|
+
It is possible to provide empty passwords by simply having nothing
|
54
|
+
listed between demarcation points:
|
55
|
+
|
56
|
+
root:@127.0.0.1
|
57
|
+
|
58
|
+
Once at the +rsql+ prompt, normal MySQL queries can be entered as
|
59
|
+
expected, ending each with a semicolon (;).
|
60
|
+
|
61
|
+
== EXAMPLE
|
62
|
+
|
63
|
+
Try walking through link:../example.rsqlrc.
|
64
|
+
|
65
|
+
== LICENSE
|
66
|
+
|
67
|
+
Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
68
|
+
|
69
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
70
|
+
a copy of this software and associated documentation files (the
|
71
|
+
"Software"), to deal in the Software without restriction, including
|
72
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
73
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
74
|
+
permit persons to whom the Software is furnished to do so, subject to
|
75
|
+
the following conditions:
|
76
|
+
|
77
|
+
The above copyright notice and this permission notice shall be
|
78
|
+
included in all copies or substantial portions of the Software.
|
79
|
+
|
80
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
81
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
82
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
83
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
84
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
85
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
86
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/TODO
CHANGED
@@ -24,9 +24,6 @@
|
|
24
24
|
|
25
25
|
* Consider using mysql's ping to determine if we need to reconnect.
|
26
26
|
|
27
|
-
* Move last_command logic into mysql_results and remember last five
|
28
|
-
(or so).
|
29
|
-
|
30
27
|
* Fix need for SSH password! It never asks.
|
31
28
|
|
32
29
|
* Find out if it's easy to point at source from the wiki to point
|
@@ -34,3 +31,7 @@
|
|
34
31
|
|
35
32
|
* Consider adding the option to specify a dispalyer when registering a
|
36
33
|
recipe.
|
34
|
+
|
35
|
+
* Move everything in here into github issues.
|
36
|
+
|
37
|
+
* Consder renaming "register" to "recipe"
|
data/bin/rsql
CHANGED
data/example.rsqlrc
CHANGED
@@ -11,22 +11,30 @@
|
|
11
11
|
|
12
12
|
# To use this file, change directory to the one containing this file,
|
13
13
|
# run rsql connecting to your MySQL server (run rsql with no arguments
|
14
|
-
# for usage).
|
15
|
-
#
|
14
|
+
# for usage).
|
15
|
+
#
|
16
|
+
# rsql> .load 'example.rsqlrc';
|
16
17
|
|
17
|
-
# After it's loaded try out the
|
18
|
-
#
|
18
|
+
# After it's loaded try listing out all the registered recipes (along
|
19
|
+
# with parameter notes and descriptions).
|
20
|
+
#
|
21
|
+
# rsql> .list;
|
19
22
|
|
20
23
|
# If you make changes to the example to try out new things (and please
|
21
|
-
# do!), you can simply
|
22
|
-
#
|
23
|
-
#
|
24
|
+
# do!), you can simply have the recipe file reloaded to have your
|
25
|
+
# changes pulled in immediately without exiting your session.
|
26
|
+
#
|
27
|
+
# rsql> .reload;
|
28
|
+
|
29
|
+
# Notice that any command issued starting with a period (.) results in
|
30
|
+
# evaluation of Ruby. Thus, any valid Ruby syntax is applicable
|
31
|
+
# following a period on a command.
|
24
32
|
|
25
33
|
################################################################################
|
26
34
|
|
27
35
|
# This type of registration is automatically invoked when this file is
|
28
36
|
# loaded. Often, this is useful to run set up routines like setting
|
29
|
-
#
|
37
|
+
# MySQL variables for different read levels (e.g. SET SESSION
|
30
38
|
# TRANSACTION ISOLATION LEVEL READ COMMITTED). Any number of these may
|
31
39
|
# be defined.
|
32
40
|
#
|
@@ -35,7 +43,8 @@
|
|
35
43
|
register_init :setup_example, %q{
|
36
44
|
CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
37
45
|
name VARCHAR(100),
|
38
|
-
value INT(11)
|
46
|
+
value INT(11),
|
47
|
+
stuff BLOB
|
39
48
|
)
|
40
49
|
}, :desc => 'Sets up example table for trying out RSQL.'
|
41
50
|
|
@@ -43,10 +52,15 @@ CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
|
43
52
|
# interpolated into it (our table name). The string will then be used
|
44
53
|
# as if typed at the command line.
|
45
54
|
#
|
55
|
+
# rsql> .cleanup_example;
|
56
|
+
#
|
46
57
|
# In this case, we are simply dropping the table created by our
|
47
|
-
# initialization recipe.
|
58
|
+
# initialization recipe. If you do this, you'll need to call the
|
59
|
+
# setup_example initialization recipe again before moving on.
|
60
|
+
#
|
61
|
+
# rsql> .setup_example;
|
48
62
|
#
|
49
|
-
register :
|
63
|
+
register :cleanup_example, %q{
|
50
64
|
DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
51
65
|
}, :desc => 'Cleans up the example table.'
|
52
66
|
|
@@ -55,8 +69,10 @@ DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
|
55
69
|
#
|
56
70
|
# Here we are just populating the table (if it isn't already).
|
57
71
|
#
|
58
|
-
#
|
59
|
-
#
|
72
|
+
# rsql> .fill_table;
|
73
|
+
#
|
74
|
+
# Notice the use of hexify and squeeze! methods available from
|
75
|
+
# EvalContext.
|
60
76
|
#
|
61
77
|
register :fill_table, :desc => 'Populate the example table.' do
|
62
78
|
sql = ''
|
@@ -64,25 +80,65 @@ register :fill_table, :desc => 'Populate the example table.' do
|
|
64
80
|
sql << "
|
65
81
|
INSERT IGNORE INTO #{@rsql_table}
|
66
82
|
SET name='fancy#{i}',
|
67
|
-
value=#{i**i}
|
83
|
+
value=#{i**i},
|
84
|
+
stuff=#{hexify(rand((i+1)**100))};
|
68
85
|
"
|
69
86
|
end
|
70
87
|
sqeeze!(sql)
|
71
88
|
end
|
72
89
|
|
90
|
+
# A very common reason for recipes is simply to add parameters to be
|
91
|
+
# dropped in to our query. To facilitate this, simply declare one or
|
92
|
+
# more variables immediately following the name of the recipe. Then
|
93
|
+
# these values can be listed by embedded interpolation points into the
|
94
|
+
# string (just as you would with any Ruby string).
|
95
|
+
#
|
96
|
+
# This call will simply return results only for those bigger than some
|
97
|
+
# value passed in.
|
98
|
+
#
|
99
|
+
# rsql> .get_big_values 80000;
|
100
|
+
#
|
101
|
+
register :get_big_values, :val, %q{
|
102
|
+
SELECT name, value FROM #{@rsql_table} WHERE #{val} <= value
|
103
|
+
}, :desc => 'Get values bigger than the one provided as an argument.'
|
104
|
+
|
105
|
+
# Sometimes we make mistakes (never!). Normally, the command history
|
106
|
+
# kept in RSQL only stores the last thing entered at the prompt--not
|
107
|
+
# any query that the previous command may have generated and invoked.
|
108
|
+
# When writing a recipe that generates a query that has an error
|
109
|
+
# reported by MySQL, it is really handy to see the query.
|
110
|
+
#
|
111
|
+
# Here's an example of a recipe that will fail. Run it and then hit the
|
112
|
+
# "up arrow" key to see the previous command.
|
113
|
+
#
|
114
|
+
# rsql> .bad_query;
|
115
|
+
#
|
116
|
+
# So the command in our history is the recipe and not the query. To
|
117
|
+
# see the query the EvalContext has a recipe ready for us:
|
118
|
+
#
|
119
|
+
# rsql> .last_query;
|
120
|
+
#
|
121
|
+
register :bad_query, %q{
|
122
|
+
SELECT name, value FROM #{@rsql_table} WHERE valu < 10000
|
123
|
+
}, :desc => 'Make a query that will result in an error.'
|
124
|
+
|
73
125
|
# After you have a table with content in it, you can run queries
|
74
126
|
# against it and have the contents changed into something a little
|
75
127
|
# more meaningful. For example, what if the values in our table were
|
76
128
|
# bytes that we wanted to humanize? Try this command:
|
77
129
|
#
|
78
|
-
#
|
130
|
+
# rsql> select name, value from rsql_example ! value => humanize_bytes;
|
79
131
|
#
|
80
132
|
# The humanize_bytes method is a helper in the EvalContext
|
81
|
-
# class. There are several others
|
133
|
+
# class. There are several others available. Check out the rdoc for
|
82
134
|
# details.
|
83
135
|
#
|
84
|
-
#
|
85
|
-
#
|
136
|
+
# Additional mappings can be added, separated by commas.
|
137
|
+
#
|
138
|
+
# You can also declare these column mappings in your recipes, though
|
139
|
+
# the syntax is slightly different, using Ruby symbols.
|
140
|
+
#
|
141
|
+
# rsql> .show_values_as_bytes;
|
86
142
|
#
|
87
143
|
register :show_values_as_bytes, %q{
|
88
144
|
SELECT value FROM #{@rsql_table}
|
@@ -95,13 +151,15 @@ SELECT value FROM #{@rsql_table}
|
|
95
151
|
# will be replaced as the column entry's content. Your method is
|
96
152
|
# called once for each value in the column from the results.
|
97
153
|
#
|
154
|
+
# rsql> .show_pretty_names;
|
155
|
+
#
|
98
156
|
# Make sure if your method doesn't understand the content passed to it
|
99
157
|
# that it just reflects it back out so you don't lose data when
|
100
158
|
# printed.
|
101
159
|
#
|
102
160
|
def pretty_names(name)
|
103
|
-
if
|
104
|
-
"#{
|
161
|
+
if m = name.match(/^(\w+)(\d+)$/)
|
162
|
+
"#{m[1]} (#{m[2]})"
|
105
163
|
else
|
106
164
|
name
|
107
165
|
end
|
@@ -112,14 +170,13 @@ SELECT name FROM #{@rsql_table}
|
|
112
170
|
}, 'name' => :pretty_names,
|
113
171
|
:desc => 'Show names separated to be more readable.'
|
114
172
|
|
115
|
-
|
116
173
|
# It's also possible to work with the full set of query results in a
|
117
174
|
# recipe. This can be useful if there is some coordination necessary
|
118
175
|
# across multiple columns to result in some new kind of report. Much
|
119
176
|
# like a shell's ability to pipe output from one command to the next,
|
120
|
-
# RSQL takes a similar
|
177
|
+
# RSQL takes a similar approach. Try this:
|
121
178
|
#
|
122
|
-
#
|
179
|
+
# rsql> select name, value from rsql_example | p @results;
|
123
180
|
#
|
124
181
|
# The EvalContext manages the results from a previous query in the
|
125
182
|
# @results member variable accessible by any Ruby recipe code. This is
|
@@ -130,9 +187,9 @@ SELECT name FROM #{@rsql_table}
|
|
130
187
|
# Here's an example that writes a simple report of the data we are
|
131
188
|
# working with. To try this out, enter the following at the prompt:
|
132
189
|
#
|
133
|
-
#
|
190
|
+
# rsql> select name, value from rsql_example | to_report;
|
134
191
|
#
|
135
|
-
register :to_report do
|
192
|
+
register :to_report, :desc => 'Report on a count of small and big values.' do
|
136
193
|
small_cnt = 0
|
137
194
|
big_cnt = 0
|
138
195
|
@results.each_hash do |row|
|
@@ -145,4 +202,78 @@ register :to_report do
|
|
145
202
|
puts "There are #{small_cnt} small values and #{big_cnt} big values."
|
146
203
|
end
|
147
204
|
|
205
|
+
# There may be other moments where it's necessary to take arguments,
|
206
|
+
# say if we want to process results and keep our data around in a
|
207
|
+
# file.
|
208
|
+
#
|
209
|
+
# rsql> select name, value from rsql_example | save_values 'myobj'
|
210
|
+
#
|
211
|
+
# After running this, a myobj.yml file should be created in the local
|
212
|
+
# directory containing all the content from the query. To accomplish
|
213
|
+
# this, the use of EvalContext's safe_save method is invoked which
|
214
|
+
# serializes our object so that we may later decided to run some post
|
215
|
+
# processing on the content.
|
216
|
+
#
|
217
|
+
register :save_values, :desc => 'Save results from a query into a file.' do |fn|
|
218
|
+
myobj = {}
|
219
|
+
@results.each_hash do |row|
|
220
|
+
myobj[row['name']] = row['value']
|
221
|
+
end
|
222
|
+
safe_save(myobj, fn)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Dealing with variable arguments is pretty straightforward as well,
|
226
|
+
# but with a little syntactic twist.
|
227
|
+
#
|
228
|
+
# rsql> .find_names 'fancy3', 'fancy8'
|
229
|
+
#
|
230
|
+
# Here we simply expand the arguments.
|
231
|
+
#
|
232
|
+
register :find_names, :'*names', %q{
|
233
|
+
SELECT name, value
|
234
|
+
FROM #{@rsql_table}
|
235
|
+
WHERE name IN (#{names.collect{|n| "'#{n}'"}.join(',')})
|
236
|
+
}, :desc => 'Find names from example table.'
|
237
|
+
|
238
|
+
# Sometimes it just isn't enough to be able to rely on generating SQL
|
239
|
+
# queries and piping into handlers. Sometimes we just need to roll up
|
240
|
+
# our sleeves and run queries directly so we can start processing
|
241
|
+
# results and dealing with presentation all on our own. That's where
|
242
|
+
# EvalContext's query helper comes in handy.
|
243
|
+
#
|
244
|
+
# The intention here is to just create a series of sentences out of
|
245
|
+
# two separate queries.
|
246
|
+
#
|
247
|
+
# rsql> .show_sentences;
|
248
|
+
#
|
249
|
+
register :show_sentences, :desc => 'Show results as sentences.' do
|
250
|
+
query("SELECT name FROM #{@rsql_table}").each_hash do |nrow|
|
251
|
+
name = nrow['name']
|
252
|
+
vals = query("SELECT value FROM #{@rsql_table} WHERE name='#{name}'")
|
253
|
+
puts "The #{name} has #{vals[0]['value']} fanciness levels."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# The MySQLResults class built in to RSQL handles binary content
|
258
|
+
# gracefully, automatically converting it to something a little nicer
|
259
|
+
# to our consoles than just dumping it. It converts it into a
|
260
|
+
# hexadecimal string.
|
261
|
+
#
|
262
|
+
# rsql> SELECT stuff FROM rsql_example
|
263
|
+
#
|
264
|
+
# The default is to limit the hex strings to 32 "bytes" reported. This
|
265
|
+
# can be configured any time by setting the @hexstr_limit.
|
266
|
+
#
|
267
|
+
# RSQL makes querying for hex strings from within a recipe easy too.
|
268
|
+
#
|
269
|
+
# rsql> .find_stuff 0x346950d3c051c8ac51092a3a2eff7503ef7f571c
|
270
|
+
#
|
271
|
+
register :find_stuff, :stuff, %q{
|
272
|
+
SELECT * FROM #{@rsql_table} WHERE stuff=#{hexify stuff}
|
273
|
+
}, :desc => 'Find some hex stuff.'
|
274
|
+
|
275
|
+
# There are many other things to try out left as an "exercise for the
|
276
|
+
# reader". Browsing the rdoc for EvalContext and MySQLResults would be
|
277
|
+
# an excellent start.
|
278
|
+
|
148
279
|
# vi: set filetype=ruby
|
data/lib/rsql.rb
CHANGED
data/lib/rsql/commands.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
#--
|
1
2
|
# Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
2
3
|
#
|
3
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -33,9 +34,11 @@ module RSQL
|
|
33
34
|
|
34
35
|
########################################
|
35
36
|
|
36
|
-
#
|
37
|
-
#
|
37
|
+
# Split commands on these characters.
|
38
38
|
SEPARATORS = ';|!'
|
39
|
+
|
40
|
+
# Split on separators, allowing for escaping;
|
41
|
+
#
|
39
42
|
def initialize(input, default_displayer)
|
40
43
|
@default_displayer = default_displayer
|
41
44
|
@cmds = []
|
data/lib/rsql/eval_context.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
#--
|
1
2
|
# Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
2
3
|
#
|
3
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -22,7 +23,7 @@ module RSQL
|
|
22
23
|
|
23
24
|
################################################################################
|
24
25
|
# This class wraps all dynamic evaluation and serves as the reflection
|
25
|
-
# class for adding
|
26
|
+
# class for adding methods dynamically.
|
26
27
|
#
|
27
28
|
class EvalContext
|
28
29
|
|
@@ -32,8 +33,8 @@ module RSQL
|
|
32
33
|
|
33
34
|
def initialize
|
34
35
|
@hexstr_limit = HEXSTR_LIMIT
|
35
|
-
@last_cmd = nil
|
36
36
|
@results = nil
|
37
|
+
@last_query = nil
|
37
38
|
|
38
39
|
@loaded_fns = []
|
39
40
|
@init_registrations = []
|
@@ -48,10 +49,10 @@ module RSQL
|
|
48
49
|
method(:reload),
|
49
50
|
'reload',
|
50
51
|
'Reload the rsqlrc file.'),
|
51
|
-
:
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
:last_query => Registration.new('last_query', [], {},
|
53
|
+
Proc.new{puts(@last_query)},
|
54
|
+
'last_query',
|
55
|
+
'Print the last query made from generated results.'),
|
55
56
|
:set_max_rows => Registration.new('set_max_rows', [], {},
|
56
57
|
Proc.new{|r| MySQLResults.max_rows = r},
|
57
58
|
'set_max_rows',
|
@@ -109,7 +110,7 @@ module RSQL
|
|
109
110
|
return val
|
110
111
|
end
|
111
112
|
|
112
|
-
#
|
113
|
+
# Safely evaluate Ruby content within our context.
|
113
114
|
#
|
114
115
|
def safe_eval(content, results, stdout)
|
115
116
|
@results = results
|
@@ -141,12 +142,12 @@ module RSQL
|
|
141
142
|
$stdout = orig_stdout if stdout
|
142
143
|
end
|
143
144
|
|
144
|
-
@
|
145
|
+
@last_query = value if String === value
|
145
146
|
|
146
147
|
return value
|
147
148
|
end
|
148
149
|
|
149
|
-
#
|
150
|
+
# Provide a list of tab completions given the prompted value.
|
150
151
|
#
|
151
152
|
def complete(str)
|
152
153
|
if str[0] == ?.
|
@@ -170,13 +171,13 @@ module RSQL
|
|
170
171
|
ret
|
171
172
|
end
|
172
173
|
|
173
|
-
#
|
174
|
+
# Reset the hexstr limit back to the default value.
|
174
175
|
#
|
175
176
|
def reset_hexstr_limit
|
176
177
|
@hexstr_limit = HEXSTR_LIMIT
|
177
178
|
end
|
178
179
|
|
179
|
-
#
|
180
|
+
# Convert a binary string value into a hexadecimal string.
|
180
181
|
#
|
181
182
|
def to_hexstr(bin, limit=@hexstr_limit, prefix='0x')
|
182
183
|
cnt = 0
|
@@ -201,9 +202,9 @@ module RSQL
|
|
201
202
|
########################################
|
202
203
|
private
|
203
204
|
|
204
|
-
#
|
205
|
+
# Display a listing of all registered helpers.
|
205
206
|
#
|
206
|
-
def list
|
207
|
+
def list # :doc:
|
207
208
|
usagelen = 0
|
208
209
|
desclen = 0
|
209
210
|
|
@@ -226,35 +227,35 @@ module RSQL
|
|
226
227
|
return nil
|
227
228
|
end
|
228
229
|
|
229
|
-
#
|
230
|
-
# software and the mysql connection
|
230
|
+
# Show all the pertinent version data we have about our
|
231
|
+
# software and the mysql connection.
|
231
232
|
#
|
232
|
-
def version
|
233
|
+
def version # :doc:
|
233
234
|
puts "rsql:v#{RSQL::VERSION} client:v#{MySQLResults.conn.client_info} " \
|
234
235
|
"server:v#{MySQLResults.conn.server_info}"
|
235
236
|
end
|
236
237
|
|
237
|
-
#
|
238
|
-
# method would like to make its own queries
|
238
|
+
# Provide a helper utility in the event a registered
|
239
|
+
# method would like to make its own queries.
|
239
240
|
#
|
240
|
-
def query(content, *args)
|
241
|
+
def query(content, *args) # :doc:
|
241
242
|
MySQLResults.query(content, self, *args)
|
242
243
|
end
|
243
244
|
|
244
|
-
#
|
245
|
+
# Exactly like register below except in addition to registering as
|
245
246
|
# a usable call for later, we will also use these as soon as we
|
246
247
|
# have a connection to MySQL.
|
247
248
|
#
|
248
|
-
def register_init(sym, *args, &block)
|
249
|
+
def register_init(sym, *args, &block) # :doc:
|
249
250
|
register(sym, *args, &block)
|
250
251
|
@init_registrations << sym
|
251
252
|
end
|
252
253
|
|
253
|
-
#
|
254
|
+
# If given a block, allow the block to be called later, otherwise,
|
254
255
|
# create a method whose sole purpose is to dynmaically generate
|
255
|
-
# sql with variable interpolation
|
256
|
+
# sql with variable interpolation.
|
256
257
|
#
|
257
|
-
def register(sym, *args, &block)
|
258
|
+
def register(sym, *args, &block) # :doc:
|
258
259
|
name = usage = sym.to_s
|
259
260
|
|
260
261
|
if Hash === args.last
|
@@ -282,9 +283,9 @@ module RSQL
|
|
282
283
|
@registrations[sym] = Registration.new(name, args, bangs, block, usage, desc)
|
283
284
|
end
|
284
285
|
|
285
|
-
#
|
286
|
+
# Convert a collection of values into hexadecimal strings.
|
286
287
|
#
|
287
|
-
def hexify(*ids)
|
288
|
+
def hexify(*ids) # :doc:
|
288
289
|
ids.collect do |id|
|
289
290
|
case id
|
290
291
|
when String
|
@@ -301,7 +302,7 @@ module RSQL
|
|
301
302
|
end.join(',')
|
302
303
|
end
|
303
304
|
|
304
|
-
#
|
305
|
+
# Convert a number of bytes into a human readable string.
|
305
306
|
#
|
306
307
|
def humanize_bytes(bytes)
|
307
308
|
abbrev = ['B','KB','MB','GB','TB','PB','EB','ZB','YB']
|
@@ -322,9 +323,9 @@ module RSQL
|
|
322
323
|
return bytes.to_s
|
323
324
|
end
|
324
325
|
|
325
|
-
#
|
326
|
+
# Convert a human readable string of bytes into an integer.
|
326
327
|
#
|
327
|
-
def dehumanize_bytes(str)
|
328
|
+
def dehumanize_bytes(str) # :doc:
|
328
329
|
abbrev = ['B','KB','MB','GB','TB','PB','EB','ZB','YB']
|
329
330
|
|
330
331
|
if str =~ /(\d+(\.\d+)?)\s*(\w+)?/
|
@@ -340,9 +341,9 @@ module RSQL
|
|
340
341
|
raise "unable to parse '#{str}'"
|
341
342
|
end
|
342
343
|
|
343
|
-
#
|
344
|
+
# Convert a time into a relative string from now.
|
344
345
|
#
|
345
|
-
def relative_time(dt)
|
346
|
+
def relative_time(dt) # :doc:
|
346
347
|
return dt unless String === dt
|
347
348
|
|
348
349
|
now = Time.now.utc
|
@@ -372,19 +373,19 @@ module RSQL
|
|
372
373
|
return "#{fmt % diff} seconds #{postfix}"
|
373
374
|
end
|
374
375
|
|
375
|
-
#
|
376
|
+
# Squeeze out any spaces.
|
376
377
|
#
|
377
|
-
def sqeeze!(sql)
|
378
|
+
def sqeeze!(sql) # :doc:
|
378
379
|
sql.gsub!(/\s+/,' ')
|
379
380
|
sql.strip!
|
380
381
|
sql << ';' unless sql[-1] == ?;
|
381
382
|
sql
|
382
383
|
end
|
383
384
|
|
384
|
-
#
|
385
|
-
# backup if the file already exists
|
385
|
+
# Safely store an object into a file keeping at most one
|
386
|
+
# backup if the file already exists.
|
386
387
|
#
|
387
|
-
def safe_save(obj, name)
|
388
|
+
def safe_save(obj, name) # :doc:
|
388
389
|
name += '.yml' unless File.extname(name) == '.yml'
|
389
390
|
tn = "#{name}.tmp"
|
390
391
|
File.open(tn, 'w'){|f| YAML.dump(obj, f)}
|
data/lib/rsql/mysql_results.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
#--
|
1
2
|
# Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
2
3
|
#
|
3
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -21,7 +22,7 @@
|
|
21
22
|
module RSQL
|
22
23
|
|
23
24
|
########################################
|
24
|
-
# A wrapper to make it easier to work with MySQL results (and prettier)
|
25
|
+
# A wrapper to make it easier to work with MySQL results (and prettier).
|
25
26
|
#
|
26
27
|
class MySQLResults
|
27
28
|
|
@@ -51,20 +52,21 @@ module RSQL
|
|
51
52
|
def max_rows; @@max_rows; end
|
52
53
|
def max_rows=(cnt); @@max_rows = cnt; end
|
53
54
|
|
54
|
-
#
|
55
|
+
# Get the name of the current database in use.
|
55
56
|
#
|
56
57
|
def database_name; @@database_name; end
|
57
58
|
|
58
|
-
#
|
59
|
+
# Get the list of databases available.
|
59
60
|
#
|
60
61
|
def databases
|
61
62
|
@@databases ||= @@conn.list_dbs.sort if @@conn
|
62
63
|
end
|
63
64
|
|
64
|
-
# get the list of tables available (if a database is
|
65
|
-
# selected) at most once every ten seconds
|
66
|
-
#
|
67
65
|
@@last_table_list = Hash.new{|h,k| h[k] = [Time.at(0), []]}
|
66
|
+
|
67
|
+
# Get the list of tables available (if a database is
|
68
|
+
# selected) at most once every ten seconds.
|
69
|
+
#
|
68
70
|
def tables(database = nil)
|
69
71
|
now = Time.now
|
70
72
|
(last, tables) = @@last_table_list[database]
|
@@ -85,8 +87,8 @@ module RSQL
|
|
85
87
|
tables
|
86
88
|
end
|
87
89
|
|
88
|
-
#
|
89
|
-
# value
|
90
|
+
# Provide a list of tab completions given the prompted
|
91
|
+
# value.
|
90
92
|
#
|
91
93
|
def complete(str)
|
92
94
|
return [] unless @@conn
|
@@ -116,7 +118,7 @@ module RSQL
|
|
116
118
|
return ret
|
117
119
|
end
|
118
120
|
|
119
|
-
#
|
121
|
+
# Get results from a query.
|
120
122
|
#
|
121
123
|
def query(sql, eval_context, raw=false, max_rows=@@max_rows)
|
122
124
|
start = Time.now.to_f
|
@@ -199,31 +201,35 @@ module RSQL
|
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
202
|
-
#
|
204
|
+
# Get the query associated with these results.
|
203
205
|
#
|
204
|
-
attr_reader :sql
|
206
|
+
attr_reader :sql
|
205
207
|
|
206
|
-
#
|
208
|
+
# Get the number of rows that were affected by the query.
|
209
|
+
#
|
210
|
+
attr_reader :affected_rows
|
211
|
+
|
212
|
+
# Determine if there are any results.
|
207
213
|
#
|
208
214
|
def any?
|
209
215
|
!@table.nil?
|
210
216
|
end
|
211
217
|
|
212
|
-
#
|
218
|
+
# Determine if there are no results.
|
213
219
|
#
|
214
220
|
def empty?
|
215
221
|
@table.nil?
|
216
222
|
end
|
217
223
|
|
218
|
-
#
|
224
|
+
# Get the number of rows available in the results.
|
219
225
|
#
|
220
226
|
def num_rows
|
221
227
|
@table ? @table.size : 0
|
222
228
|
end
|
223
229
|
|
224
|
-
#
|
230
|
+
# Get a row from the table hashed with the field names.
|
225
231
|
#
|
226
|
-
def
|
232
|
+
def [](index)
|
227
233
|
hash = {}
|
228
234
|
if @fields && @table
|
229
235
|
row = @table[index]
|
@@ -232,8 +238,8 @@ module RSQL
|
|
232
238
|
return hash
|
233
239
|
end
|
234
240
|
|
235
|
-
#
|
236
|
-
# names
|
241
|
+
# Iterate through each row of the table hashed with the field
|
242
|
+
# names.
|
237
243
|
#
|
238
244
|
def each_hash(&block)
|
239
245
|
if @table
|
@@ -245,7 +251,7 @@ module RSQL
|
|
245
251
|
end
|
246
252
|
end
|
247
253
|
|
248
|
-
#
|
254
|
+
# Show a set of results in a decent fashion.
|
249
255
|
#
|
250
256
|
def display_by_column(io=$stdout)
|
251
257
|
if @fields && @table
|
@@ -268,7 +274,7 @@ module RSQL
|
|
268
274
|
end
|
269
275
|
end
|
270
276
|
|
271
|
-
#
|
277
|
+
# Show a set of results with a single character separation.
|
272
278
|
#
|
273
279
|
def display_by_batch(io=$stdout)
|
274
280
|
if @fields && @table
|
@@ -277,7 +283,7 @@ module RSQL
|
|
277
283
|
end
|
278
284
|
end
|
279
285
|
|
280
|
-
#
|
286
|
+
# Show a set of results line separated.
|
281
287
|
#
|
282
288
|
def display_by_line(io=$stdout)
|
283
289
|
if @fields && @table
|
@@ -297,6 +303,8 @@ module RSQL
|
|
297
303
|
display_stats(io)
|
298
304
|
end
|
299
305
|
|
306
|
+
# Show a summary line of the results.
|
307
|
+
#
|
300
308
|
def display_stats(io=$stdout, hdr='')
|
301
309
|
if @table
|
302
310
|
if @database_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: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 5
|
10
|
+
version: 0.1.5
|
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: 2011-05-
|
18
|
+
date: 2011-05-16 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
type: :runtime
|
35
35
|
version_requirements: *id001
|
36
36
|
description: |
|
37
|
-
|
37
|
+
RSQL makes working with a MySQL command line more convenient through
|
38
38
|
the use of recipes and embedding the common operation of using a SSH
|
39
39
|
connection to an intermediary host for access to the MySQL server.
|
40
40
|
|
@@ -43,11 +43,11 @@ executables:
|
|
43
43
|
- rsql
|
44
44
|
extensions: []
|
45
45
|
|
46
|
-
extra_rdoc_files:
|
47
|
-
|
46
|
+
extra_rdoc_files:
|
47
|
+
- README.rdoc
|
48
48
|
files:
|
49
49
|
- LICENSE
|
50
|
-
- README.
|
50
|
+
- README.rdoc
|
51
51
|
- TODO
|
52
52
|
- bin/rsql
|
53
53
|
- example.rsqlrc
|
@@ -60,8 +60,11 @@ homepage: https://github.com/bradrf/rsql
|
|
60
60
|
licenses: []
|
61
61
|
|
62
62
|
post_install_message:
|
63
|
-
rdoc_options:
|
64
|
-
|
63
|
+
rdoc_options:
|
64
|
+
- --title
|
65
|
+
- RSQL Documentation
|
66
|
+
- --main
|
67
|
+
- README.rdoc
|
65
68
|
require_paths:
|
66
69
|
- lib
|
67
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
data/README.txt
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
= rsql
|
2
|
-
|
3
|
-
https://github.com/bradrf/rsql
|
4
|
-
|
5
|
-
== DESCRIPTION
|
6
|
-
|
7
|
-
This is an application to make working with a SQL command line more
|
8
|
-
convenient by allowing interaction with Ruby in addition to embedding
|
9
|
-
the common operation of using a SSH connection to an intermediary host
|
10
|
-
for access to the SQL server.
|
11
|
-
|
12
|
-
== SYNOPSIS
|
13
|
-
|
14
|
-
Run rsql with no arguments for usage.
|
15
|
-
|
16
|
-
Aside from the standard MySQL command syntax, the following
|
17
|
-
functionality allows for a little more expressive processing.
|
18
|
-
|
19
|
-
Multiple commands can be issued in one set by separation with
|
20
|
-
semicolons.
|
21
|
-
|
22
|
-
Generating SQL
|
23
|
-
--------------
|
24
|
-
|
25
|
-
Ruby code may be called to generate the SQL that is to be executed.
|
26
|
-
This is done by starting any command string with a period. If the
|
27
|
-
final result of evaluating the command string is another string, it is
|
28
|
-
executed as SQL. Any semicolons meant to be processed by Ruby must be
|
29
|
-
escaped. Example:
|
30
|
-
|
31
|
-
rsql> . puts 'hello world!' \; 'select * from Account';
|
32
|
-
|
33
|
-
Utilizing Canned Methods (aka "Recipes")
|
34
|
-
----------------------------------------
|
35
|
-
|
36
|
-
Commands can be stored in the .rsqlrc file in your HOME directory to
|
37
|
-
expose methods that may be invoked to generate SQL with variable
|
38
|
-
interpolation. Use of the 'register' helper is recommended for this
|
39
|
-
approach. These can then be called in the same way as above. Example:
|
40
|
-
|
41
|
-
In the .rsqlrc file...
|
42
|
-
|
43
|
-
register :users_by_email, :email %q{
|
44
|
-
SELECT * FROM Users WHERE email = '#{email}'
|
45
|
-
}
|
46
|
-
|
47
|
-
...then from the prompt:
|
48
|
-
|
49
|
-
rsql> . users_by_email 'brad@gigglewax.com';
|
50
|
-
|
51
|
-
If a block is provided to the registration, it will be called as a
|
52
|
-
method. Example:
|
53
|
-
|
54
|
-
In the .sqlrc file...
|
55
|
-
|
56
|
-
register :dummy, :hello do |*args|
|
57
|
-
p args
|
58
|
-
end
|
59
|
-
|
60
|
-
rsql> . dummy :world;
|
61
|
-
|
62
|
-
All registered methods can be listed using the built-in 'list'
|
63
|
-
command.
|
64
|
-
|
65
|
-
Changes to a sourced file can be reloaded using the built-in 'reload'
|
66
|
-
command.
|
67
|
-
|
68
|
-
Processing Column Data
|
69
|
-
----------------------
|
70
|
-
|
71
|
-
Ruby can be called to process any data on a per-column basis before a
|
72
|
-
displayer is used to render the output. In this way, one can write
|
73
|
-
Ruby to act like MySQL functions on all the data for a given column,
|
74
|
-
converting it into a more readable value. A bang indicator (exlamation
|
75
|
-
point: !) is used to demarcate a mapping of column names to Ruby
|
76
|
-
methods that should be invoked to processes content. Example:
|
77
|
-
|
78
|
-
rsql> select IpAddress from Devices ! IpAddress => bin_to_str;
|
79
|
-
|
80
|
-
This will call 'bin_to_str' for each 'IpAddress' returned from the
|
81
|
-
query. Mulitple mappings are separated by a comma. These mappings can
|
82
|
-
also be utilized in a canned method. Example:
|
83
|
-
|
84
|
-
register :all_ips, 'select IpAddress from Devices', 'IpAddress' => :bin_to_str
|
85
|
-
|
86
|
-
Redirection
|
87
|
-
-----------
|
88
|
-
|
89
|
-
Output from one or more queries may be post-processed dynamically. If
|
90
|
-
any set of commands is follwed by a pipe symbol, results from the
|
91
|
-
previous command will be available in a member variable as
|
92
|
-
@results. The results can be used through any of the MySQLResults
|
93
|
-
class' public methods (e.g. each_hash) and the final Ruby code will be
|
94
|
-
evaluated with access to them. Any result of the evaluation that is a
|
95
|
-
string is then executed as SQL. Example:
|
96
|
-
|
97
|
-
rsql> select * from Users | @results.each_hash{|r| p r};
|
98
|
-
|
99
|
-
And again, it's most useful to encapsulate the processing logic within
|
100
|
-
a registered ruby block that can be called after the pipe as a single
|
101
|
-
command.
|
102
|
-
|
103
|
-
Even More!
|
104
|
-
----------
|
105
|
-
|
106
|
-
For additional examples and functionality, read and try the
|
107
|
-
example.rsqlrc file.
|
108
|
-
|
109
|
-
== LICENSE
|
110
|
-
|
111
|
-
Copyright (C) 2011 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
112
|
-
|
113
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
114
|
-
a copy of this software and associated documentation files (the
|
115
|
-
"Software"), to deal in the Software without restriction, including
|
116
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
117
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
118
|
-
permit persons to whom the Software is furnished to do so, subject to
|
119
|
-
the following conditions:
|
120
|
-
|
121
|
-
The above copyright notice and this permission notice shall be
|
122
|
-
included in all copies or substantial portions of the Software.
|
123
|
-
|
124
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
125
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
126
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
127
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
128
|
-
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
129
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
130
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|