rsql 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -11,7 +11,7 @@ for access to the SQL server.
11
11
 
12
12
  == SYNOPSIS
13
13
 
14
- See rsql -help for usage.
14
+ Run rsql with no arguments for usage.
15
15
 
16
16
  Aside from the standard MySQL command syntax, the following
17
17
  functionality allows for a little more expressive processing.
@@ -28,10 +28,10 @@ final result of evaluating the command string is another string, it is
28
28
  executed as SQL. Any semicolons meant to be processed by Ruby must be
29
29
  escaped. Example:
30
30
 
31
- rsql> . puts 'hello world!' \\; 'select * from Account'
31
+ rsql> . puts 'hello world!' \; 'select * from Account';
32
32
 
33
- Utilizing Canned Methods
34
- ------------------------
33
+ Utilizing Canned Methods (aka "Recipes")
34
+ ----------------------------------------
35
35
 
36
36
  Commands can be stored in the .rsqlrc file in your HOME directory to
37
37
  expose methods that may be invoked to generate SQL with variable
@@ -41,23 +41,23 @@ approach. These can then be called in the same way as above. Example:
41
41
  In the .rsqlrc file...
42
42
 
43
43
  register :users_by_email, :email %q{
44
- SELECT * FROM Users WHERE email = '\#\{email\}'
44
+ SELECT * FROM Users WHERE email = '#{email}'
45
45
  }
46
46
 
47
47
  ...then from the prompt:
48
48
 
49
- rsql> . users_by_email 'brad@gigglewax.com'
49
+ rsql> . users_by_email 'brad@gigglewax.com';
50
50
 
51
51
  If a block is provided to the registration, it will be called as a
52
52
  method. Example:
53
53
 
54
54
  In the .sqlrc file...
55
55
 
56
- register :dumby, :hello do |*args|
56
+ register :dummy, :hello do |*args|
57
57
  p args
58
58
  end
59
59
 
60
- rsql> . dumby :world
60
+ rsql> . dummy :world;
61
61
 
62
62
  All registered methods can be listed using the built-in 'list'
63
63
  command.
@@ -75,7 +75,7 @@ converting it into a more readable value. A bang indicator (exlamation
75
75
  point: !) is used to demarcate a mapping of column names to Ruby
76
76
  methods that should be invoked to processes content. Example:
77
77
 
78
- rsql> select IpAddress from Devices ; ! IpAddress => bin_to_str
78
+ rsql> select IpAddress from Devices ! IpAddress => bin_to_str;
79
79
 
80
80
  This will call 'bin_to_str' for each 'IpAddress' returned from the
81
81
  query. Mulitple mappings are separated by a comma. These mappings can
@@ -87,13 +87,24 @@ Redirection
87
87
  -----------
88
88
 
89
89
  Output from one or more queries may be post-processed dynamically. If
90
- any set of commands is follwed by a greater-than symbol, all results
91
- will be stored in a global $results array (with field information
92
- stored in $fields) and the final Ruby code will be evaluated with
93
- access to them. Any result of the evaluation that is a string is then
94
- executed as SQL. Example:
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:
95
96
 
96
- rsql> select * from Account; select * from Users; > $results.each {|r| p r}
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.
97
108
 
98
109
  == LICENSE
99
110
 
data/TODO CHANGED
@@ -1,5 +1,3 @@
1
- * Support separation of semicolon content from registered sql.
2
-
3
1
  * Fix multiline dump to be columnar sensitive (i.e. all but the first
4
2
  line will need a prefix of previous column length whitespace.
5
3
 
@@ -28,3 +26,11 @@
28
26
 
29
27
  * Move last_command logic into mysql_results and remember last five
30
28
  (or so).
29
+
30
+ * Fix need for SSH password! It never asks.
31
+
32
+ * Find out if it's easy to point at source from the wiki to point
33
+ people at the example.rsqlrc.
34
+
35
+ * Consider adding the option to specify a dispalyer when registering a
36
+ recipe.
data/bin/rsql CHANGED
@@ -56,7 +56,7 @@ else
56
56
  rc_fn = File.join(ENV['HOME'], ".#{bn}rc")
57
57
  end
58
58
 
59
- eval_context.load(rc_fn) if File.exists?(rc_fn)
59
+ eval_context.load(rc_fn, false) if File.exists?(rc_fn)
60
60
 
61
61
  def get_password(prompt)
62
62
  iswin = nil != (RUBY_PLATFORM =~ /(win|w)32$/)
@@ -256,7 +256,7 @@ rescue Mysql::Error => ex
256
256
  exit 1
257
257
  end
258
258
 
259
- eval_context.call_init_registrations(MySQLResults.conn)
259
+ eval_context.call_init_registrations
260
260
 
261
261
  history_fn = File.join(ENV['HOME'], ".#{bn}_history")
262
262
  if File.exists?(history_fn) && 0 < File.size(history_fn)
data/example.rsqlrc ADDED
@@ -0,0 +1,148 @@
1
+ # -*- Mode: ruby -*-
2
+
3
+ # This file is meant to be a working illustration of how RSQL might be
4
+ # used and to show off various features of the application.
5
+
6
+ # All examples below will use this temporary table. You will need to
7
+ # "use" a database first before loading this file since it will need
8
+ # to create this temporary table.
9
+ #
10
+ @rsql_table = 'rsql_example'
11
+
12
+ # To use this file, change directory to the one containing this file,
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).
16
+
17
+ # After it's loaded try out the command ".list;" (without the
18
+ # quotes).
19
+
20
+ # 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
+
25
+ ################################################################################
26
+
27
+ # This type of registration is automatically invoked when this file is
28
+ # loaded. Often, this is useful to run set up routines like setting
29
+ # mysql variables for different read levels (e.g. SET SESSION
30
+ # TRANSACTION ISOLATION LEVEL READ COMMITTED). Any number of these may
31
+ # be defined.
32
+ #
33
+ # Here we are merely setting up the example table.
34
+ #
35
+ register_init :setup_example, %q{
36
+ CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
37
+ name VARCHAR(100),
38
+ value INT(11)
39
+ )
40
+ }, :desc => 'Sets up example table for trying out RSQL.'
41
+
42
+ # This recipe is simply building up a string with a single variable
43
+ # interpolated into it (our table name). The string will then be used
44
+ # as if typed at the command line.
45
+ #
46
+ # In this case, we are simply dropping the table created by our
47
+ # initialization recipe.
48
+ #
49
+ register :cleanup, %q{
50
+ DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
51
+ }, :desc => 'Cleans up the example table.'
52
+
53
+ # This is an example of a recipe that utilizes a Ruby block for
54
+ # running code to generate the SQL we eventually return.
55
+ #
56
+ # Here we are just populating the table (if it isn't already).
57
+ #
58
+ # Notice that we are also making use of one of EvalContext's helper
59
+ # methods, "squeeze!" to make the SQL compact.
60
+ #
61
+ register :fill_table, :desc => 'Populate the example table.' do
62
+ sql = ''
63
+ 10.times do |i|
64
+ sql << "
65
+ INSERT IGNORE INTO #{@rsql_table}
66
+ SET name='fancy#{i}',
67
+ value=#{i**i};
68
+ "
69
+ end
70
+ sqeeze!(sql)
71
+ end
72
+
73
+ # After you have a table with content in it, you can run queries
74
+ # against it and have the contents changed into something a little
75
+ # more meaningful. For example, what if the values in our table were
76
+ # bytes that we wanted to humanize? Try this command:
77
+ #
78
+ # rsql> select * from rsql_example ! value => humanize_bytes;
79
+ #
80
+ # The humanize_bytes method is a helper in the EvalContext
81
+ # class. There are several others avaialble. Check out the rdoc for
82
+ # details.
83
+ #
84
+ # You can also declare these column mappings in your recipes too,
85
+ # though the syntax is slightly different, using Ruby symbols.
86
+ #
87
+ register :show_values_as_bytes, %q{
88
+ SELECT value FROM #{@rsql_table}
89
+ }, 'value' => :humanize_bytes,
90
+ :desc => 'Show values as humanized bytes.'
91
+
92
+ # It is even possible to make up your own column mapping helpers. Just
93
+ # create a Ruby method and reference it as a symbol mapped to whatever
94
+ # column the helper is expecting for content. The return of the helper
95
+ # will be replaced as the column entry's content. Your method is
96
+ # called once for each value in the column from the results.
97
+ #
98
+ # Make sure if your method doesn't understand the content passed to it
99
+ # that it just reflects it back out so you don't lose data when
100
+ # printed.
101
+ #
102
+ def pretty_names(name)
103
+ if name =~ /^(\w+)(\d+)$/
104
+ "#{$1} (#{$2})"
105
+ else
106
+ name
107
+ end
108
+ end
109
+
110
+ register :show_pretty_names, %q{
111
+ SELECT name FROM #{@rsql_table}
112
+ }, 'name' => :pretty_names,
113
+ :desc => 'Show names separated to be more readable.'
114
+
115
+
116
+ # It's also possible to work with the full set of query results in a
117
+ # recipe. This can be useful if there is some coordination necessary
118
+ # across multiple columns to result in some new kind of report. Much
119
+ # like a shell's ability to pipe output from one command to the next,
120
+ # RSQL takes a similar approch. Try this:
121
+ #
122
+ # rsql> select * from rsql_example | p @results;
123
+ #
124
+ # The EvalContext manages the results from a previous query in the
125
+ # @results member variable accessible by any Ruby recipe code. This is
126
+ # an instance of the MySQLResults class. Below we make use of the
127
+ # each_hash method to walk over all rows. There are other helpful
128
+ # routines available as well that are documented in rdoc.
129
+ #
130
+ # Here's an example that writes a simple report of the data we are
131
+ # working with. To try this out, enter the following at the prompt:
132
+ #
133
+ # rsql> select * from rsql_example | to_report;
134
+ #
135
+ register :to_report do
136
+ small_cnt = 0
137
+ big_cnt = 0
138
+ @results.each_hash do |row|
139
+ if row['value'].to_i < 10000
140
+ small_cnt +=1
141
+ else
142
+ big_cnt += 1
143
+ end
144
+ end
145
+ puts "There are #{small_cnt} small values and #{big_cnt} big values."
146
+ end
147
+
148
+ # vi: set filetype=ruby
data/lib/rsql/commands.rb CHANGED
@@ -118,6 +118,14 @@ module RSQL
118
118
  return @cmds.empty?
119
119
  end
120
120
 
121
+ def concat(other)
122
+ @cmds.concat(other)
123
+ end
124
+
125
+ def last
126
+ @cmds.last
127
+ end
128
+
121
129
  def run!(eval_context)
122
130
  last_results = nil
123
131
  while @cmds.any?
@@ -150,9 +158,6 @@ module RSQL
150
158
  when ?.
151
159
  content.slice!(0)
152
160
  declarator = :ruby
153
- when ?@
154
- content.slice!(0)
155
- declarator = :iterator
156
161
  else
157
162
  declarator = is_ruby ? :ruby : nil
158
163
  end
@@ -176,39 +181,42 @@ module RSQL
176
181
  end
177
182
 
178
183
  def run_command(cmd, last_results, eval_context)
179
- ctx = EvalContext::CommandContext.new
180
-
181
- # set up to allow an iterator to run up to 100,000 times
182
- 100000.times do |i|
183
- eval_context.bangs = cmd.bangs
184
-
185
- if cmd.declarator
186
- ctx.index = i
187
- ctx.last_results = last_results
188
- stdout = cmd.displayer == :pipe ? StringIO.new : nil
189
- value = eval_context.safe_eval(cmd.content, ctx, stdout)
190
- else
191
- value = cmd.content
192
- end
193
-
194
- return :done if value == 'exit' || value == 'quit'
184
+ eval_context.bangs = cmd.bangs
195
185
 
186
+ if cmd.declarator
187
+ stdout = cmd.displayer == :pipe ? StringIO.new : nil
188
+ value = eval_context.safe_eval(cmd.content, last_results, stdout)
196
189
  if String === value
197
- begin
198
- last_results = MySQLResults.query(value, eval_context)
199
- rescue MySQLResults::MaxRowsException => ex
200
- $stderr.puts "refusing to process #{ex.rows} rows (max: #{ex.max})"
201
- rescue MysqlError => ex
202
- $stderr.puts ex.message
203
- rescue Exception => ex
204
- $stderr.puts ex.inspect
205
- raise
190
+ cmds = Commands.new(value, @default_displayer)
191
+ unless cmds.empty?
192
+ # need to carry along the bangs into the
193
+ # last command so we don't lose them
194
+ if cmds.last.bangs.empty? && cmd.bangs.any?
195
+ cmds.last.bangs = cmd.bangs
196
+ end
197
+ @cmds = cmds.concat(@cmds)
206
198
  end
207
- else
208
- last_results = EvalResults.new(value, stdout)
199
+ return
209
200
  end
201
+ else
202
+ value = cmd.content
203
+ end
210
204
 
211
- break unless ctx.incomplete
205
+ return :done if value == 'exit' || value == 'quit'
206
+
207
+ if String === value
208
+ begin
209
+ last_results = MySQLResults.query(value, eval_context)
210
+ rescue MySQLResults::MaxRowsException => ex
211
+ $stderr.puts "refusing to process #{ex.rows} rows (max: #{ex.max})"
212
+ rescue MysqlError => ex
213
+ $stderr.puts ex.message
214
+ rescue Exception => ex
215
+ $stderr.puts ex.inspect
216
+ raise
217
+ end
218
+ else
219
+ last_results = EvalResults.new(value, stdout)
212
220
  end
213
221
 
214
222
  return last_results
@@ -28,13 +28,12 @@ module RSQL
28
28
 
29
29
  Registration = Struct.new(:name, :args, :bangs, :block, :usage, :desc)
30
30
 
31
- CommandContext = Struct.new(:index, :incomplete, :last_results, :state)
32
-
33
31
  HEXSTR_LIMIT = 32
34
32
 
35
33
  def initialize
36
34
  @hexstr_limit = HEXSTR_LIMIT
37
35
  @last_cmd = nil
36
+ @results = nil
38
37
 
39
38
  @loaded_fns = []
40
39
  @init_registrations = []
@@ -66,15 +65,15 @@ module RSQL
66
65
 
67
66
  attr_accessor :bangs
68
67
 
69
- def call_init_registrations(mysql)
68
+ def call_init_registrations
70
69
  @init_registrations.each do |sym|
71
70
  reg = @registrations[sym]
72
71
  sql = reg.block.call(*reg.args)
73
- mysql.query(sql) if String === sql
72
+ query(sql) if String === sql
74
73
  end
75
74
  end
76
75
 
77
- def load(fn)
76
+ def load(fn, init=true)
78
77
  ret = Thread.new {
79
78
  begin
80
79
  eval(File.read(fn), binding, fn)
@@ -89,11 +88,12 @@ module RSQL
89
88
  $stderr.puts("#{ret.class}: #{ret.message}", bt, '')
90
89
  else
91
90
  @loaded_fns << fn unless @loaded_fns.include?(fn)
91
+ call_init_registrations if init
92
92
  end
93
93
  end
94
94
 
95
95
  def reload
96
- @loaded_fns.each{|fn| self.load(fn)}
96
+ @loaded_fns.each{|fn| self.load(fn, false)}
97
97
  puts "loaded: #{@loaded_fns.inspect}"
98
98
  end
99
99
 
@@ -111,8 +111,8 @@ module RSQL
111
111
 
112
112
  # safely evaluate Ruby content within our context
113
113
  #
114
- def safe_eval(content, context, stdout)
115
- @command_context = context
114
+ def safe_eval(content, results, stdout)
115
+ @results = results
116
116
 
117
117
  # allow a simple reload to be called directly as it requires a
118
118
  # little looser safety valve...
File without changes
data/lib/rsql.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module RSQL
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
 
4
4
  require 'rsql/mysql'
5
5
  require 'rsql/mysql_results'
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: 29
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 3
10
- version: 0.1.3
9
+ - 4
10
+ version: 0.1.4
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-13 00:00:00 Z
18
+ date: 2011-05-15 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: net-ssh
@@ -39,8 +39,8 @@ description: |
39
39
  connection to an intermediary host for access to the MySQL server.
40
40
 
41
41
  email: brad+rsql@gigglewax.com
42
- executables: []
43
-
42
+ executables:
43
+ - rsql
44
44
  extensions: []
45
45
 
46
46
  extra_rdoc_files: []
@@ -50,6 +50,7 @@ files:
50
50
  - README.txt
51
51
  - TODO
52
52
  - bin/rsql
53
+ - example.rsqlrc
53
54
  - lib/rsql.rb
54
55
  - lib/rsql/commands.rb
55
56
  - lib/rsql/eval_context.rb
@@ -86,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
87
  requirements: []
87
88
 
88
89
  rubyforge_project:
89
- rubygems_version: 1.7.2
90
+ rubygems_version: 1.8.2
90
91
  signing_key:
91
92
  specification_version: 3
92
93
  summary: Ruby based MySQL command line with recipes.