rsql 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|