rsql 0.1.4 → 0.1.5
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 +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.
|