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.
@@ -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
@@ -96,11 +96,6 @@ def split_login(str)
96
96
  end
97
97
  end
98
98
 
99
- if ARGV.delete('-help')
100
- eval_context.help
101
- exit
102
- end
103
-
104
99
  if ARGV.delete('-version')
105
100
  puts "#{bn} v#{RSQL::VERSION}"
106
101
  exit
@@ -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). At the RSQL prompt, type ".load 'example.rsqlrc';"
15
- # (without the double quotes).
14
+ # for usage).
15
+ #
16
+ # rsql> .load 'example.rsqlrc';
16
17
 
17
- # After it's loaded try out the command ".list;" (without the
18
- # quotes).
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 enter ".reload;" (without the double quotes) to
22
- # have your changes pulled in immediately without exiting your
23
- # session.
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
- # mysql variables for different read levels (e.g. SET SESSION
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 :cleanup, %q{
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
- # Notice that we are also making use of one of EvalContext's helper
59
- # methods, "squeeze!" to make the SQL compact.
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
- # rsql> select * from rsql_example ! value => humanize_bytes;
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 avaialble. Check out the rdoc for
133
+ # class. There are several others available. Check out the rdoc for
82
134
  # details.
83
135
  #
84
- # You can also declare these column mappings in your recipes too,
85
- # though the syntax is slightly different, using Ruby symbols.
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 name =~ /^(\w+)(\d+)$/
104
- "#{$1} (#{$2})"
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 approch. Try this:
177
+ # RSQL takes a similar approach. Try this:
121
178
  #
122
- # rsql> select * from rsql_example | p @results;
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
- # rsql> select * from rsql_example | to_report;
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
@@ -1,5 +1,8 @@
1
+ # A module encapsulating classes to manage MySQLResults and process
2
+ # Commands using an EvalContext for handling recipes.
3
+ #
1
4
  module RSQL
2
- VERSION = '0.1.4'
5
+ VERSION = '0.1.5'
3
6
 
4
7
  require 'rsql/mysql'
5
8
  require 'rsql/mysql_results'
@@ -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
- # split on separators, allowing for escaping
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 = []
@@ -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 new methods dynamically.
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
- :last_cmd => Registration.new('last_cmd', [], {},
52
- Proc.new{puts @last_cmd},
53
- 'last_cmd',
54
- 'Print the last command generated.'),
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
- # safely evaluate Ruby content within our context
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
- @last_cmd = value if String === value
145
+ @last_query = value if String === value
145
146
 
146
147
  return value
147
148
  end
148
149
 
149
- # provide a list of tab completions given the prompted value
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
- # reset the hexstr limit back to the default value
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
- # convert a binary string value into a hexadecimal string
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
- # display a listing of all registered helpers
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
- # show all the pertinent version data we have about our
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
- # provide a helper utility in the event a registered
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
- # exactly like register below except in addition to registering as
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
- # if given a block, allow the block to be called later, otherwise,
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
- # convert a collection of values into to hexadecimal strings
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
- # convert a number of bytes into a human readable string
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
- # convert a human readable string of bytes into an integer
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
- # convert a time into a relative string from now
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
- # squeeze out any spaces
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
- # safely store an object into a file keeping at most one
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)}
@@ -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
- # get the name of the current database in use
55
+ # Get the name of the current database in use.
55
56
  #
56
57
  def database_name; @@database_name; end
57
58
 
58
- # get the list of databases available
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
- # provide a list of tab completions given the prompted
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
- # get results from a query
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
- # get the number of rows that were affected by the query
204
+ # Get the query associated with these results.
203
205
  #
204
- attr_reader :sql, :affected_rows
206
+ attr_reader :sql
205
207
 
206
- # determine if there are any results
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
- # determine if there are no results
218
+ # Determine if there are no results.
213
219
  #
214
220
  def empty?
215
221
  @table.nil?
216
222
  end
217
223
 
218
- # get the number of rows available in the results
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
- # get a row from the table hashed with the field names
230
+ # Get a row from the table hashed with the field names.
225
231
  #
226
- def row_hash(index)
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
- # iterate through each row of the table hashed with the field
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
- # show a set of results in a decent fashion
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
- # show a set of results with a single character separation
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
- # show a set of results line separated
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: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 4
10
- version: 0.1.4
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-15 00:00:00 Z
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
- Rsql makes working with a MySQL command line more convenient through
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.txt
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.