rsql 0.2.7 → 0.2.8
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/LICENSE +19 -0
- data/example.rsqlrc +291 -0
- data/extra/mysql-client-5.1.59-1.tgz +0 -0
- data/lib/rsql.rb +10 -0
- data/lib/rsql/commands.rb +243 -0
- data/lib/rsql/eval_context.rb +737 -0
- data/lib/rsql/mysql_results.rb +470 -0
- data/test/test_commands.rb +85 -0
- data/test/test_eval_context.rb +179 -0
- data/test/test_mysql_results.rb +192 -0
- metadata +17 -10
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011-2012 by brad+rsql@gigglewax.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/example.rsqlrc
ADDED
@@ -0,0 +1,291 @@
|
|
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).
|
15
|
+
#
|
16
|
+
# rsql> .load 'example.rsqlrc';
|
17
|
+
|
18
|
+
# After it's loaded try listing out all the registered recipes (along
|
19
|
+
# with parameter notes and descriptions).
|
20
|
+
#
|
21
|
+
# rsql> .list;
|
22
|
+
|
23
|
+
# If you make changes to the example to try out new things (and please
|
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.
|
32
|
+
|
33
|
+
################################################################################
|
34
|
+
|
35
|
+
# This type of registration is automatically invoked when this file is
|
36
|
+
# loaded. Often, this is useful to run set up routines like setting
|
37
|
+
# MySQL variables for different read levels (e.g. SET SESSION
|
38
|
+
# TRANSACTION ISOLATION LEVEL READ COMMITTED). Any number of these may
|
39
|
+
# be defined.
|
40
|
+
#
|
41
|
+
# Here we are merely setting up the example table.
|
42
|
+
#
|
43
|
+
register_init :setup_example, %q{
|
44
|
+
CREATE TEMPORARY TABLE IF NOT EXISTS #{@rsql_table} (
|
45
|
+
name VARCHAR(100),
|
46
|
+
value INT(11),
|
47
|
+
stuff BLOB
|
48
|
+
)
|
49
|
+
}, :desc => 'Sets up example table for trying out RSQL.'
|
50
|
+
|
51
|
+
# This recipe is simply building up a string with a single variable
|
52
|
+
# interpolated into it (our table name). The string will then be used
|
53
|
+
# as if typed at the command line.
|
54
|
+
#
|
55
|
+
# rsql> .cleanup_example;
|
56
|
+
#
|
57
|
+
# In this case, we are simply dropping the table created by our
|
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;
|
62
|
+
#
|
63
|
+
register :cleanup_example, %q{
|
64
|
+
DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}
|
65
|
+
}, :desc => 'Cleans up the example table.'
|
66
|
+
|
67
|
+
# This is an example of a recipe that utilizes a Ruby block for
|
68
|
+
# running code to generate the SQL we eventually return.
|
69
|
+
#
|
70
|
+
# Here we are just populating the table (if it isn't already).
|
71
|
+
#
|
72
|
+
# rsql> .fill_table;
|
73
|
+
#
|
74
|
+
# Notice the use of hexify and squeeze! methods available from
|
75
|
+
# EvalContext.
|
76
|
+
#
|
77
|
+
register :fill_table, :desc => 'Populate the example table.' do
|
78
|
+
sql = ''
|
79
|
+
9.times do |i|
|
80
|
+
sql << %{
|
81
|
+
INSERT IGNORE INTO #{@rsql_table}
|
82
|
+
SET name='fancy#{i}',
|
83
|
+
value=#{i**i},
|
84
|
+
stuff=#{hexify(rand((i+1)**100))};
|
85
|
+
}
|
86
|
+
end
|
87
|
+
# one more that isn't randomly generated so we can reference it
|
88
|
+
# later
|
89
|
+
sql << %{
|
90
|
+
INSERT IGNORE INTO #{@rsql_table}
|
91
|
+
SET name='fancy9',
|
92
|
+
value=#{9**9},
|
93
|
+
stuff=0x1234567891234567891234567890;
|
94
|
+
}
|
95
|
+
squeeze!(sql)
|
96
|
+
end
|
97
|
+
|
98
|
+
# A very common reason for recipes is simply to add parameters to be
|
99
|
+
# dropped in to our query. To facilitate this, simply declare one or
|
100
|
+
# more variables immediately following the name of the recipe. Then
|
101
|
+
# these values can be listed by embedded interpolation points into the
|
102
|
+
# string (just as you would with any Ruby string).
|
103
|
+
#
|
104
|
+
# This call will simply return results only for those bigger than some
|
105
|
+
# value passed in.
|
106
|
+
#
|
107
|
+
# rsql> .get_big_values 80000;
|
108
|
+
#
|
109
|
+
register :get_big_values, :val, %q{
|
110
|
+
SELECT name, value FROM #{@rsql_table} WHERE #{val} <= value
|
111
|
+
}, :desc => 'Get values bigger than the one provided as an argument.'
|
112
|
+
|
113
|
+
# Sometimes we make mistakes (never!). Normally, the command history
|
114
|
+
# kept in RSQL only stores the last thing entered at the prompt--not
|
115
|
+
# any query that the previous command may have generated and invoked.
|
116
|
+
# When writing a recipe that generates a query that has an error
|
117
|
+
# reported by MySQL, it is really handy to see the query.
|
118
|
+
#
|
119
|
+
# Here's an example of a recipe that will fail. Run it and then hit the
|
120
|
+
# "up arrow" key to see the previous command.
|
121
|
+
#
|
122
|
+
# rsql> .bad_query;
|
123
|
+
#
|
124
|
+
# So the command in our history is the recipe and not the query. To
|
125
|
+
# see the query the EvalContext has a recipe ready for us:
|
126
|
+
#
|
127
|
+
# rsql> .history;
|
128
|
+
#
|
129
|
+
register :bad_query, %q{
|
130
|
+
SELECT name, value FROM #{@rsql_table} WHERE valu < 10000
|
131
|
+
}, :desc => 'Make a query that will result in an error.'
|
132
|
+
|
133
|
+
# After you have a table with content in it, you can run queries
|
134
|
+
# against it and have the contents changed into something a little
|
135
|
+
# more meaningful. For example, what if the values in our table were
|
136
|
+
# bytes that we wanted to humanize? Try this command:
|
137
|
+
#
|
138
|
+
# rsql> select name, value from rsql_example ! value => humanize_bytes;
|
139
|
+
#
|
140
|
+
# The humanize_bytes method is a helper in the EvalContext
|
141
|
+
# class. There are several others available. Check out the rdoc for
|
142
|
+
# details.
|
143
|
+
#
|
144
|
+
# Additional mappings can be added, separated by commas.
|
145
|
+
#
|
146
|
+
# You can also declare these column mappings in your recipes, though
|
147
|
+
# the syntax is slightly different, using Ruby symbols.
|
148
|
+
#
|
149
|
+
# rsql> .show_values_as_bytes;
|
150
|
+
#
|
151
|
+
register :show_values_as_bytes, %q{
|
152
|
+
SELECT value FROM #{@rsql_table}
|
153
|
+
}, 'value' => :humanize_bytes,
|
154
|
+
:desc => 'Show values as humanized bytes.'
|
155
|
+
|
156
|
+
# It is even possible to make up your own column mapping helpers. Just
|
157
|
+
# create a Ruby method and reference it as a symbol mapped to whatever
|
158
|
+
# column the helper is expecting for content. The return of the helper
|
159
|
+
# will be replaced as the column entry's content. Your method is
|
160
|
+
# called once for each value in the column from the results.
|
161
|
+
#
|
162
|
+
# rsql> .show_pretty_names;
|
163
|
+
#
|
164
|
+
# Make sure if your method doesn't understand the content passed to it
|
165
|
+
# that it just reflects it back out so you don't lose data when
|
166
|
+
# printed.
|
167
|
+
#
|
168
|
+
def pretty_names(name)
|
169
|
+
if m = name.match(/^(\w+)(\d+)$/)
|
170
|
+
"#{m[1]} (#{m[2]})"
|
171
|
+
else
|
172
|
+
name
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
register :show_pretty_names, %q{
|
177
|
+
SELECT name FROM #{@rsql_table}
|
178
|
+
}, 'name' => :pretty_names,
|
179
|
+
:desc => 'Show names separated to be more readable.'
|
180
|
+
|
181
|
+
# It's also possible to work with the full set of query results in a
|
182
|
+
# recipe. This can be useful if there is some coordination necessary
|
183
|
+
# across multiple columns to result in some new kind of report. Much
|
184
|
+
# like a shell's ability to pipe output from one command to the next,
|
185
|
+
# RSQL takes a similar approach. Try this:
|
186
|
+
#
|
187
|
+
# rsql> select name, value from rsql_example | p @results;
|
188
|
+
#
|
189
|
+
# The EvalContext manages the results from a previous query in the
|
190
|
+
# @results member variable accessible by any Ruby recipe code. This is
|
191
|
+
# an instance of the MySQLResults class. Below we make use of the
|
192
|
+
# each_hash method to walk over all rows. There are other helpful
|
193
|
+
# routines available as well that are documented in rdoc.
|
194
|
+
#
|
195
|
+
# Here's an example that writes a simple report of the data we are
|
196
|
+
# working with. To try this out, enter the following at the prompt:
|
197
|
+
#
|
198
|
+
# rsql> select name, value from rsql_example | to_report;
|
199
|
+
#
|
200
|
+
register :to_report, :desc => 'Report on a count of small and big values.' do
|
201
|
+
small_cnt = 0
|
202
|
+
big_cnt = 0
|
203
|
+
@results.each_hash do |row|
|
204
|
+
if row['value'].to_i < 10000
|
205
|
+
small_cnt +=1
|
206
|
+
else
|
207
|
+
big_cnt += 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
puts "There are #{small_cnt} small values and #{big_cnt} big values."
|
211
|
+
end
|
212
|
+
|
213
|
+
# There may be other moments where it's necessary to take arguments,
|
214
|
+
# say if we want to process results and keep our data around in a
|
215
|
+
# file.
|
216
|
+
#
|
217
|
+
# rsql> select name, value from rsql_example | save_values 'myobj';
|
218
|
+
#
|
219
|
+
# After running this, a myobj.yml file should be created in the local
|
220
|
+
# directory containing all the content from the query. To accomplish
|
221
|
+
# this, the use of EvalContext's safe_save method is invoked which
|
222
|
+
# serializes our object so that we may later decided to run some post
|
223
|
+
# processing on the content.
|
224
|
+
#
|
225
|
+
# Inspect the YAML content written out:
|
226
|
+
#
|
227
|
+
# rsql> .puts IO.read('myobj.yml');
|
228
|
+
#
|
229
|
+
register :save_values, :desc => 'Save results from a query into a file.' do |fn|
|
230
|
+
myobj = {}
|
231
|
+
@results.each_hash do |row|
|
232
|
+
myobj[row['name']] = row['value']
|
233
|
+
end
|
234
|
+
safe_save(myobj, fn)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Dealing with variable arguments is pretty straightforward as well,
|
238
|
+
# but with a little syntactic twist.
|
239
|
+
#
|
240
|
+
# rsql> .find_names 'fancy3', 'fancy8';
|
241
|
+
#
|
242
|
+
# Here we simply expand the arguments.
|
243
|
+
#
|
244
|
+
register :find_names, :'*names', %q{
|
245
|
+
SELECT name, value
|
246
|
+
FROM #{@rsql_table}
|
247
|
+
WHERE name IN (#{names.collect{|n| "'#{n}'"}.join(',')})
|
248
|
+
}, :desc => 'Find names from example table.'
|
249
|
+
|
250
|
+
# Sometimes it just isn't enough to be able to rely on generating SQL
|
251
|
+
# queries and piping into handlers. Sometimes we just need to roll up
|
252
|
+
# our sleeves and run queries directly so we can start processing
|
253
|
+
# results and dealing with presentation all on our own. That's where
|
254
|
+
# EvalContext's query helper comes in handy.
|
255
|
+
#
|
256
|
+
# The intention here is to just create a series of sentences out of
|
257
|
+
# two separate queries.
|
258
|
+
#
|
259
|
+
# rsql> .show_sentences;
|
260
|
+
#
|
261
|
+
register :show_sentences, :desc => 'Show results as sentences.' do
|
262
|
+
query("SELECT name FROM #{@rsql_table}").each_hash do |nrow|
|
263
|
+
name = nrow['name']
|
264
|
+
vals = query("SELECT value FROM #{@rsql_table} WHERE name='#{name}'")
|
265
|
+
puts "The #{name} has #{vals[0]['value']} fanciness levels."
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# The MySQLResults class built in to RSQL handles binary content
|
270
|
+
# gracefully, automatically converting it to something a little nicer
|
271
|
+
# to our consoles than just dumping it. It converts it into a
|
272
|
+
# hexadecimal string.
|
273
|
+
#
|
274
|
+
# rsql> SELECT stuff FROM rsql_example;
|
275
|
+
#
|
276
|
+
# The default is to limit the hex strings to 32 "bytes" reported. This
|
277
|
+
# can be configured any time by setting the @hexstr_limit.
|
278
|
+
#
|
279
|
+
# RSQL makes querying for hex strings from within a recipe easy too.
|
280
|
+
#
|
281
|
+
# rsql> .find_stuff 0x1234567891234567891234567890;
|
282
|
+
#
|
283
|
+
register :find_stuff, :stuff, %q{
|
284
|
+
SELECT * FROM #{@rsql_table} WHERE stuff=#{hexify stuff}
|
285
|
+
}, :desc => 'Find some hex stuff.'
|
286
|
+
|
287
|
+
# There are many other things to try out left as an "exercise for the
|
288
|
+
# reader". Browsing the rdoc for EvalContext and MySQLResults would be
|
289
|
+
# an excellent start.
|
290
|
+
|
291
|
+
# vi: set filetype=ruby
|
Binary file
|
data/lib/rsql.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2011-2012 by Brad Robel-Forrest <brad+rsql@gigglewax.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
module RSQL
|
23
|
+
|
24
|
+
require 'stringio'
|
25
|
+
|
26
|
+
EvalResults = Struct.new(:value, :stdout)
|
27
|
+
|
28
|
+
########################################
|
29
|
+
# A wrapper to parse and handle commands
|
30
|
+
#
|
31
|
+
class Commands
|
32
|
+
|
33
|
+
Command = Struct.new(:content, :bangs, :declarator, :displayer)
|
34
|
+
|
35
|
+
########################################
|
36
|
+
|
37
|
+
# Split commands on these characters.
|
38
|
+
SEPARATORS = ';|!'
|
39
|
+
|
40
|
+
# Split on separators, allowing for escaping;
|
41
|
+
#
|
42
|
+
def initialize(input, default_displayer)
|
43
|
+
@default_displayer = default_displayer
|
44
|
+
@cmds = []
|
45
|
+
esc = ''
|
46
|
+
bangs = {}
|
47
|
+
match_before_bang = nil
|
48
|
+
in_pipe_arg = false
|
49
|
+
next_is_ruby = false
|
50
|
+
|
51
|
+
input.scan(/[^#{SEPARATORS}]+.?/) do |match|
|
52
|
+
orig_match = match
|
53
|
+
|
54
|
+
if i = SEPARATORS.index(match[-1])
|
55
|
+
sep = SEPARATORS[i]
|
56
|
+
match.chop!
|
57
|
+
|
58
|
+
if match[-1] == ?\\
|
59
|
+
# unescape the separator and save the content away
|
60
|
+
esc << match[0..-2] << sep
|
61
|
+
next
|
62
|
+
end
|
63
|
+
else
|
64
|
+
sep = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
unless esc.empty?
|
68
|
+
esc << match
|
69
|
+
match = esc
|
70
|
+
esc = ''
|
71
|
+
end
|
72
|
+
|
73
|
+
if match_before_bang
|
74
|
+
new_bangs = {}
|
75
|
+
match.split(/\s*,\s*/).each do |ent|
|
76
|
+
(key,val) = ent.split(/\s*=>\s*/)
|
77
|
+
unless key && val
|
78
|
+
# they are using a bang but have no maps
|
79
|
+
# so we assume this is a != or something
|
80
|
+
# similar and let it go through unmapped
|
81
|
+
esc = match_before_bang + '!' + match
|
82
|
+
match_before_bang = nil
|
83
|
+
break
|
84
|
+
end
|
85
|
+
if val.strip == 'nil'
|
86
|
+
new_bangs[key.strip] = nil
|
87
|
+
else
|
88
|
+
new_bangs[key.strip] = val.to_sym
|
89
|
+
end
|
90
|
+
end
|
91
|
+
next unless match_before_bang
|
92
|
+
match = match_before_bang
|
93
|
+
match_before_bang = nil
|
94
|
+
bangs.merge!(new_bangs)
|
95
|
+
end
|
96
|
+
|
97
|
+
if sep == ?!
|
98
|
+
match_before_bang = match
|
99
|
+
next
|
100
|
+
end
|
101
|
+
|
102
|
+
if sep == ?|
|
103
|
+
# we've split on a pipe so we need to handle the
|
104
|
+
# case where ruby code is declaring a block with
|
105
|
+
# arguments (e.g. {|x| p x} or do |x| p x end)
|
106
|
+
if in_pipe_arg
|
107
|
+
in_pipe_arg = false
|
108
|
+
esc << match << '|'
|
109
|
+
next
|
110
|
+
elsif orig_match =~ /\{\s*|do\s*/
|
111
|
+
in_pipe_arg = true
|
112
|
+
esc << match << '|'
|
113
|
+
next
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
add_command(match, bangs, next_is_ruby, sep)
|
118
|
+
|
119
|
+
bangs = {}
|
120
|
+
next_is_ruby = sep == ?|
|
121
|
+
end
|
122
|
+
|
123
|
+
add_command(esc, bangs, next_is_ruby)
|
124
|
+
end
|
125
|
+
|
126
|
+
def empty?
|
127
|
+
return @cmds.empty?
|
128
|
+
end
|
129
|
+
|
130
|
+
def concat(other)
|
131
|
+
@cmds.concat(other)
|
132
|
+
end
|
133
|
+
|
134
|
+
def last
|
135
|
+
@cmds.last
|
136
|
+
end
|
137
|
+
|
138
|
+
def run!(eval_context)
|
139
|
+
last_results = nil
|
140
|
+
while @cmds.any?
|
141
|
+
cmd = @cmds.shift
|
142
|
+
results = run_command(cmd, last_results, eval_context)
|
143
|
+
return :done if results == :done
|
144
|
+
|
145
|
+
if cmd.displayer == :pipe
|
146
|
+
last_results = results
|
147
|
+
elsif MySQLResults === results
|
148
|
+
last_results = nil
|
149
|
+
results.send(cmd.displayer)
|
150
|
+
elsif EvalResults === results
|
151
|
+
last_results = nil
|
152
|
+
if MySQLResults === results.value
|
153
|
+
# This happens if their recipe returns MySQL
|
154
|
+
# results...just display it like above.
|
155
|
+
results.value.send(cmd.displayer)
|
156
|
+
else
|
157
|
+
if results.stdout && 0 < results.stdout.size
|
158
|
+
puts results.stdout.string
|
159
|
+
end
|
160
|
+
puts "=> #{results.value.inspect}" if results.value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
########################################
|
167
|
+
private
|
168
|
+
|
169
|
+
def add_command(content, bangs, is_ruby, separator=nil)
|
170
|
+
content.strip!
|
171
|
+
|
172
|
+
case content[0]
|
173
|
+
when ?.
|
174
|
+
content.slice!(0)
|
175
|
+
declarator = :ruby
|
176
|
+
else
|
177
|
+
declarator = is_ruby ? :ruby : nil
|
178
|
+
end
|
179
|
+
|
180
|
+
if content.end_with?('\G')
|
181
|
+
# emulate mysql's \G output
|
182
|
+
content.slice!(-2,2)
|
183
|
+
displayer = :display_by_line
|
184
|
+
elsif separator == ?|
|
185
|
+
displayer = :pipe
|
186
|
+
else
|
187
|
+
displayer = @default_displayer
|
188
|
+
end
|
189
|
+
|
190
|
+
unless content.empty?
|
191
|
+
@cmds << Command.new(content, bangs, declarator, displayer)
|
192
|
+
return true
|
193
|
+
end
|
194
|
+
|
195
|
+
return false
|
196
|
+
end
|
197
|
+
|
198
|
+
def run_command(cmd, last_results, eval_context)
|
199
|
+
eval_context.bangs = cmd.bangs
|
200
|
+
|
201
|
+
if cmd.declarator
|
202
|
+
stdout = cmd.displayer == :pipe ? StringIO.new : nil
|
203
|
+
value = eval_context.safe_eval(cmd.content, last_results, stdout)
|
204
|
+
if String === value
|
205
|
+
cmds = Commands.new(value, cmd.displayer)
|
206
|
+
unless cmds.empty?
|
207
|
+
# need to carry along the bangs into the
|
208
|
+
# last command so we don't lose them
|
209
|
+
if cmds.last.bangs.empty? && cmd.bangs.any?
|
210
|
+
cmds.last.bangs = cmd.bangs
|
211
|
+
end
|
212
|
+
@cmds = cmds.concat(@cmds)
|
213
|
+
end
|
214
|
+
return
|
215
|
+
end
|
216
|
+
else
|
217
|
+
value = cmd.content
|
218
|
+
end
|
219
|
+
|
220
|
+
return :done if value == 'exit' || value == 'quit'
|
221
|
+
|
222
|
+
if String === value
|
223
|
+
begin
|
224
|
+
last_results = MySQLResults.query(value, eval_context)
|
225
|
+
rescue MySQLResults::MaxRowsException => ex
|
226
|
+
$stderr.puts "refusing to process #{ex.rows} rows (max: #{ex.max})--" <<
|
227
|
+
"consider raising this via set_max_rows"
|
228
|
+
rescue Mysql::Error => ex
|
229
|
+
$stderr.puts ex.message
|
230
|
+
rescue Exception => ex
|
231
|
+
$stderr.puts ex.inspect
|
232
|
+
raise
|
233
|
+
end
|
234
|
+
else
|
235
|
+
last_results = EvalResults.new(value, stdout)
|
236
|
+
end
|
237
|
+
|
238
|
+
return last_results
|
239
|
+
end
|
240
|
+
|
241
|
+
end # class Commands
|
242
|
+
|
243
|
+
end # module RSQL
|