assert_efficient_sql 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/assert_efficient_sql.rb +129 -51
- data/test/assert_efficient_sql_test.rb +384 -0
- metadata +8 -5
data/lib/assert_efficient_sql.rb
CHANGED
@@ -8,7 +8,7 @@ require 'assert2'
|
|
8
8
|
# ERGO check for valid options
|
9
9
|
# ERGO highlite the offending row in the analysis
|
10
10
|
# ERGO cite http://hackmysql.com/selectandsort
|
11
|
-
# ERGO link from http://efficient-sql.rubyforge.org/files/README.html to
|
11
|
+
# ERGO link from http://efficient-sql.rubyforge.org/files/README.html to
|
12
12
|
# project page
|
13
13
|
# ERGO hamachi.cc
|
14
14
|
# ERGO is flunk susceptible to <?> bug?
|
@@ -60,7 +60,7 @@ module ActiveRecord
|
|
60
60
|
|
61
61
|
# TODO use extended?
|
62
62
|
# http://www.mysqlperformanceblog.com/2006/07/24/extended-explain/
|
63
|
-
#
|
63
|
+
#
|
64
64
|
# ERGO p select_without_analyzer('show warnings')
|
65
65
|
# hence, show warnings caused by your queries, too
|
66
66
|
|
@@ -73,7 +73,7 @@ module ActiveRecord
|
|
73
73
|
end
|
74
74
|
) if sql =~ /^select/i
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
explained = [sql, name, analysis.map(&:with_indifferent_access)]
|
78
78
|
(@analyses ||= []) << explained
|
79
79
|
end
|
@@ -87,14 +87,14 @@ end
|
|
87
87
|
module AssertEfficientSql; end
|
88
88
|
|
89
89
|
class AssertEfficientSql::SqlEfficiencyAsserter
|
90
|
-
|
90
|
+
|
91
91
|
def initialize(options, context)
|
92
92
|
@issues = []
|
93
93
|
@options, @context = options, context
|
94
94
|
@analyses = ActiveRecord::Base.connection.analyses
|
95
95
|
@session_before = fetch_database_session
|
96
96
|
yield
|
97
|
-
check_for_query_statements
|
97
|
+
check_for_query_statements
|
98
98
|
@session_after = fetch_database_session
|
99
99
|
check_session_status
|
100
100
|
|
@@ -103,7 +103,7 @@ class AssertEfficientSql::SqlEfficiencyAsserter
|
|
103
103
|
analyze_efficiency
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
puts explain_all if @options[:verbose]
|
108
108
|
end
|
109
109
|
|
@@ -138,27 +138,27 @@ class AssertEfficientSql::SqlEfficiencyAsserter
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
def analyze_efficiency
|
143
143
|
rows = @explanation[:rows].to_i
|
144
144
|
throttle = @options[:throttle]
|
145
|
-
|
145
|
+
|
146
146
|
check rows <= throttle do
|
147
147
|
"row count #{ rows } is more than :throttle => #{ throttle }"
|
148
148
|
end
|
149
|
-
|
150
|
-
check @options[:ALL] || 'ALL' != @explanation[:type] do
|
151
|
-
'full table scan'
|
149
|
+
|
150
|
+
check @options[:ALL] || 'ALL' != @explanation[:type] do
|
151
|
+
'full table scan'
|
152
152
|
end
|
153
|
-
|
153
|
+
|
154
154
|
check @options[:Using_filesort] ||
|
155
155
|
@explanation[:Extra] !~ /(Using filesort)/ do
|
156
156
|
$1
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
flunk 'Pessimistic ' + format_explanation unless @issues.empty?
|
160
160
|
end
|
161
|
-
|
161
|
+
|
162
162
|
def check_for_query_statements
|
163
163
|
flunk 'assert_efficient_sql saw no queries!' if @analyses.empty?
|
164
164
|
end
|
@@ -166,35 +166,37 @@ class AssertEfficientSql::SqlEfficiencyAsserter
|
|
166
166
|
def flunk(why)
|
167
167
|
@context.flunk @context.build_message(@options[:diagnostic], why)
|
168
168
|
end
|
169
|
-
|
169
|
+
|
170
170
|
def format_explanation
|
171
171
|
@name = 'for ' + @name unless @name.blank?
|
172
172
|
|
173
173
|
return "\nquery #{ @name }\n" +
|
174
174
|
@issues.join("\n") +
|
175
|
-
"\n#{ @sql }\n " +
|
175
|
+
"\n#{ @sql }\n " +
|
176
176
|
@analysis.qa_columnized
|
177
177
|
end
|
178
178
|
|
179
179
|
end
|
180
|
-
|
180
|
+
|
181
181
|
#:startdoc:
|
182
182
|
|
183
183
|
|
184
184
|
module AssertEfficientSql
|
185
|
-
|
186
|
-
#
|
187
|
-
|
185
|
+
|
186
|
+
# This collects every model in the given class that appears while its block runs
|
188
187
|
def assert_latest(*models, &block)
|
189
188
|
models, diagnostic = _get_latest_args(models, 'assert')
|
190
|
-
get_latest(models, &block)
|
189
|
+
latests = get_latest(models, &block)
|
190
|
+
latests.compact.length == models.length or
|
191
|
+
_flunk_latest(models, latests, diagnostic, true, block)
|
192
|
+
return *latests
|
191
193
|
end
|
192
194
|
|
193
|
-
def _get_latest_args(models, what)
|
195
|
+
def _get_latest_args(models, what) #:nodoc:
|
194
196
|
diagnostic = nil
|
195
197
|
diagnostic = models.pop if models.last.kind_of? String
|
196
|
-
|
197
|
-
unless models.length > 0 and
|
198
|
+
|
199
|
+
unless models.length > 0 and
|
198
200
|
(diagnostic.nil? or diagnostic.kind_of? String)
|
199
201
|
raise "call #{ what }_latest(models..., diagnostic) with any number " +
|
200
202
|
'of Model classes, followed by an optional diagnostic message'
|
@@ -202,49 +204,55 @@ module AssertEfficientSql
|
|
202
204
|
return models, diagnostic
|
203
205
|
end
|
204
206
|
private :_get_latest_args
|
205
|
-
|
207
|
+
|
206
208
|
def deny_latest(*models, &block)
|
207
209
|
models, diagnostic = _get_latest_args(models, 'deny')
|
208
|
-
|
209
|
-
|
210
|
-
|
210
|
+
latests = get_latest(models, &block)
|
211
|
+
return if latests.compact.empty?
|
212
|
+
models = [latests].flatten.compact.map(&:class)
|
213
|
+
_flunk_latest(models, latests, diagnostic, false, block)
|
211
214
|
end
|
212
215
|
|
213
216
|
def get_latest(models, &block)
|
214
217
|
max_ids = models.map{|model| model.maximum(:id) || 0 }
|
215
218
|
block.call
|
216
219
|
index = -1
|
217
|
-
return
|
220
|
+
return models.map{|model|
|
218
221
|
all = *model.find( :all,
|
219
222
|
:conditions => "id > #{max_ids[index += 1]}",
|
220
223
|
:order => "id asc" )
|
221
|
-
all # * returns nil for [],
|
222
|
-
|
224
|
+
all # * returns nil for [],
|
225
|
+
# one object for [x],
|
223
226
|
# or an array with more than one item
|
224
227
|
}
|
225
228
|
end
|
226
|
-
|
227
|
-
def _flunk_latest(models, diagnostic, polarity, block)
|
228
|
-
model_names = models.map(&:name)
|
229
|
-
|
229
|
+
|
230
|
+
def _flunk_latest(models, latests, diagnostic, polarity, block) #:nodoc:
|
231
|
+
model_names = models.map(&:name)
|
232
|
+
model_names.each_with_index do |it, index|
|
233
|
+
model_names[index] = nil if !!latests[index] == polarity
|
234
|
+
end
|
235
|
+
model_names = model_names.compact.join(', ')
|
236
|
+
rationale = "should#{ ' not' unless polarity
|
230
237
|
} create new #{ model_names
|
231
|
-
} record(s) in block:\n\t\t#{
|
232
|
-
reflect_source(&block).gsub("\n", "\n\t\t")
|
238
|
+
} record(s) in block:\n\t\t#{
|
239
|
+
reflect_source(&block).gsub("\n", "\n\t\t")
|
233
240
|
}\n"
|
234
241
|
# RubyNodeReflector::RubyReflector.new(block, false).result }"
|
235
242
|
# note we don't evaluate...
|
236
243
|
flunk build_message(diagnostic, rationale)
|
237
244
|
end
|
238
245
|
private :_flunk_latest
|
239
|
-
|
240
|
-
|
246
|
+
|
247
|
+
|
241
248
|
def _exec(cmd) #:nodoc:
|
242
249
|
ActiveRecord::Base.connection.execute(cmd)
|
243
250
|
end
|
244
251
|
|
252
|
+
# See: http://www.oreillynet.com/onlamp/blog/2007/07/assert_latest_and_greatest.html
|
245
253
|
def assert_efficient_sql(options = {}, &block)
|
246
254
|
options = { :verbose => true } if options == :verbose
|
247
|
-
|
255
|
+
|
248
256
|
if options.class == Hash
|
249
257
|
options.reverse_merge! default_options
|
250
258
|
|
@@ -257,13 +265,83 @@ module AssertEfficientSql
|
|
257
265
|
else
|
258
266
|
print_syntax
|
259
267
|
end
|
260
|
-
|
268
|
+
|
261
269
|
return []
|
262
270
|
end
|
263
271
|
|
272
|
+
def self._singulars #:nodoc:
|
273
|
+
return [ [:ids, :id ], [:key_lens, :key_len ],
|
274
|
+
[:keys, :key ], [:refs, :ref ],
|
275
|
+
[:rows, :rows ], [:select_types, :select_type],
|
276
|
+
[:tables, :table ], [:types, :type ] ]
|
277
|
+
end
|
278
|
+
|
279
|
+
def self._multiples #:nodoc:
|
280
|
+
return [ [:extras, :Extra ],
|
281
|
+
[:possible_keys, :possible_keys] ]
|
282
|
+
end
|
283
|
+
|
284
|
+
# def test_order_transactions_by_id
|
285
|
+
# sqls = inspect_sql :match => /SELECT conversations.\*/ do
|
286
|
+
# get_sales_after_transaction_id
|
287
|
+
# end
|
288
|
+
# statement = sqls.first[:statement]
|
289
|
+
# assert{ statement =~ /ORDER BY id asc/ }
|
290
|
+
# end
|
291
|
+
#
|
292
|
+
def inspect_sql(options = {}, &block)
|
293
|
+
verbose = options.fetch(:verbose, false)
|
294
|
+
match = options.fetch(:match, /./)
|
295
|
+
args = { :ALL => true, :Using_filesort => true, :verbose => verbose }
|
296
|
+
inspections = assert_efficient_sql(args, &block)
|
297
|
+
# pp inspections.first
|
298
|
+
|
299
|
+
explains = inspections.inject([]) do |a, inspection|
|
300
|
+
if inspection.first =~ match
|
301
|
+
explanations = inspection.last
|
302
|
+
|
303
|
+
h = { :statement => inspection.first,
|
304
|
+
:explanations => explanations }
|
305
|
+
|
306
|
+
AssertEfficientSql._singulars.each do |rollup, explainer|
|
307
|
+
h[rollup] = explanations.map{|e| q = e[explainer] and q.to_sym }
|
308
|
+
end
|
309
|
+
|
310
|
+
AssertEfficientSql._multiples.each do |rollup, explainer|
|
311
|
+
h[rollup] = explanations.map do |e|
|
312
|
+
q = e[explainer]
|
313
|
+
[q].flatten.map do |e|
|
314
|
+
e and
|
315
|
+
e.any? and
|
316
|
+
(rollup == :extras ? e.gsub!(' ', '_') : e ) and
|
317
|
+
e.to_sym
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
a << h
|
322
|
+
end
|
323
|
+
a
|
324
|
+
end
|
325
|
+
return AssertEfficientSql._decorate_explainers(explains)
|
326
|
+
end
|
327
|
+
|
328
|
+
def self._decorate_explainers(explains) #:nodoc:
|
329
|
+
def explains.find_statements(regexp)
|
330
|
+
return AssertEfficientSql._decorate_explainers(select{|explanation|
|
331
|
+
explanation[:statement] =~ regexp
|
332
|
+
})
|
333
|
+
end
|
334
|
+
|
335
|
+
(_multiples + _singulars).each do |rollup, explainer|
|
336
|
+
eval "def explains.#{rollup}; map{|q|q[:#{rollup}]}.flatten.uniq; end"
|
337
|
+
end
|
338
|
+
|
339
|
+
return explains
|
340
|
+
end # FIXME gotta be private
|
341
|
+
|
264
342
|
class BufferStdout #:nodoc:
|
265
343
|
def write(stuff)
|
266
|
-
(@output ||= '') << stuff
|
344
|
+
(@output ||= '') << stuff
|
267
345
|
end
|
268
346
|
def output; @output || '' end
|
269
347
|
end
|
@@ -284,19 +362,19 @@ module AssertEfficientSql
|
|
284
362
|
end
|
285
363
|
|
286
364
|
private
|
287
|
-
|
365
|
+
|
288
366
|
def current_adapter?(type) #:nodoc:
|
289
367
|
ActiveRecord::ConnectionAdapters.const_defined?(type) and
|
290
368
|
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
|
291
369
|
end
|
292
|
-
|
370
|
+
|
293
371
|
def warn_adapter_required(options)
|
294
372
|
if options[:warn_mysql_required]
|
295
373
|
puts 'assert_efficient_sql requires MySQL' unless $warned_once
|
296
374
|
$warned_once = true
|
297
|
-
end
|
375
|
+
end
|
298
376
|
end
|
299
|
-
|
377
|
+
|
300
378
|
def assert_efficient_mysql(options, &block)
|
301
379
|
outer_block_analyses = ActiveRecord::Base.connection.analyses
|
302
380
|
ActiveRecord::Base.connection.analyses = []
|
@@ -306,7 +384,7 @@ module AssertEfficientSql
|
|
306
384
|
ensure
|
307
385
|
ActiveRecord::Base.connection.analyses = outer_block_analyses
|
308
386
|
end
|
309
|
-
|
387
|
+
|
310
388
|
def syntax
|
311
389
|
return {
|
312
390
|
:diagnostic => [nil , 'supplementary message in failure reports'],
|
@@ -316,22 +394,22 @@ module AssertEfficientSql
|
|
316
394
|
:verbose => [false, 'if the test passes, print the EXPLAIN'],
|
317
395
|
:warn_mysql_required => [true , 'disable the spew advising we only work with MySQL'] }
|
318
396
|
end
|
319
|
-
|
397
|
+
|
320
398
|
def default_options
|
321
399
|
options = syntax.dup
|
322
400
|
options.each{|k,(v,m)| options[k] = v}
|
323
401
|
return options
|
324
402
|
end
|
325
|
-
|
403
|
+
|
326
404
|
def print_syntax
|
327
405
|
puts "\n\nassert_efficient_sql called with invalid argument.\n"
|
328
406
|
puts " __flag__ __default__ __effect__"
|
329
|
-
|
407
|
+
|
330
408
|
syntax.each do |k,(v,m)|
|
331
409
|
printf " :%-14s => %-8s # %s\n", k, v.inspect, m
|
332
410
|
end
|
333
411
|
end
|
334
|
-
|
412
|
+
|
335
413
|
end
|
336
414
|
|
337
415
|
#:stopdoc:
|
@@ -0,0 +1,384 @@
|
|
1
|
+
|
2
|
+
#:stopdoc:
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/connection_adapters/mysql_adapter.rb'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'pathname'
|
9
|
+
require 'yaml'
|
10
|
+
require 'optparse'
|
11
|
+
require 'erb'
|
12
|
+
Assert_efficient_sql_root = (Pathname.new(__FILE__).expand_path.dirname + '..').expand_path
|
13
|
+
load Assert_efficient_sql_root + 'lib/assert_efficient_sql.rb'
|
14
|
+
|
15
|
+
# ERGO even work if you are sqLite3
|
16
|
+
class Foo < ActiveRecord::Base; end
|
17
|
+
class T2 < ActiveRecord::Base; self.table_name ='t2'; end
|
18
|
+
|
19
|
+
#:startdoc:
|
20
|
+
|
21
|
+
class AssertEfficientSqlTest < Test::Unit::TestCase
|
22
|
+
|
23
|
+
def setup #:nodoc:
|
24
|
+
config_file = (Assert_efficient_sql_root + 'config/database.yml').to_s
|
25
|
+
config = YAML.load(ERB.new(IO.read(config_file)).result)['test']
|
26
|
+
ActiveRecord::Base.establish_connection(config)
|
27
|
+
|
28
|
+
ActiveRecord::Base.connection.
|
29
|
+
create_table(:foos) do |t|
|
30
|
+
t.column :name, :string, :limit => 60
|
31
|
+
end
|
32
|
+
|
33
|
+
43.times{ Foo.create!(:name => 'whatever') }
|
34
|
+
_exec %[ CREATE TABLE `t2` (
|
35
|
+
`id` int(11) auto_increment,
|
36
|
+
`a` int(11) NOT NULL DEFAULT '0',
|
37
|
+
`b` blob,
|
38
|
+
`c` int(11) NOT NULL,
|
39
|
+
PRIMARY KEY (id)
|
40
|
+
) ENGINE=InnoDB ] rescue nil
|
41
|
+
#system('mysql -u root beast_test')
|
42
|
+
end
|
43
|
+
|
44
|
+
# If +assert_efficient_sql+ (generally) dislikes your arguments,
|
45
|
+
# it will print out its default options, each with an explanation
|
46
|
+
#
|
47
|
+
def test_help
|
48
|
+
assert_stdout /invalid.*argument.*
|
49
|
+
verbose .* =\> .* false/mx do
|
50
|
+
assert_efficient_sql(:help){}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_assert_efficient_sql
|
55
|
+
assert_efficient_sql{ Foo.find(2) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# If your SQL is already efficient, use <b>:verbose</b> to diagnose
|
59
|
+
# <i>why</i> it's efficient.
|
60
|
+
#
|
61
|
+
def test_verbose
|
62
|
+
assert_stdout /select_type/ do
|
63
|
+
assert_efficient_sql :verbose do
|
64
|
+
Foo.find_by_id(42)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# If your block did not call any SQL SELECT statements,
|
70
|
+
# you probably need a warning!
|
71
|
+
#
|
72
|
+
def test_require_sql
|
73
|
+
assert_flunked /no queries/ do
|
74
|
+
assert_efficient_sql{}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# This test case uses
|
79
|
+
# <code>assert_raise_message[http://www.oreillynet.com/onlamp/blog/2007/07/assert_raise_on_ruby_dont_just.html]</code>
|
80
|
+
# to demonstrate <code>assert_efficient_sql</code> failing:
|
81
|
+
#
|
82
|
+
def test_assert_inefficient_sql
|
83
|
+
assert_flunked /Pessimistic.*
|
84
|
+
full.table.scan.*
|
85
|
+
Foo.Load/mx do
|
86
|
+
assert_efficient_sql do
|
87
|
+
Foo.find_by_sql('select * from foos a')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# One common pessimization is a query that reads thousands
|
93
|
+
# of rows just to return a few. +assert_efficient_sql+
|
94
|
+
# counts the rows hit in each phase of an SQL +SELECT+,
|
95
|
+
# and faults if any row count exceeds <b>1,000</b>.
|
96
|
+
#
|
97
|
+
# Adjust this count with <b><code>:throttle => 42</code></b>.
|
98
|
+
#
|
99
|
+
def test_throttle
|
100
|
+
101.times{|x| Foo.create :name => "foo_#{ x }" }
|
101
|
+
|
102
|
+
assert_flunked /Pessimistic.*
|
103
|
+
more.than.*100.*
|
104
|
+
Foo.Load/mx do
|
105
|
+
assert_efficient_sql :throttle => 100, :ALL => true do
|
106
|
+
Foo.find(:all)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sometimes you need an +ALL+, even while other <code>assert_efficient_sql</code>
|
112
|
+
# checks must pass. To positively declare we like +ALL+, pass it as the key of a
|
113
|
+
# +true+ option into the assertion:
|
114
|
+
#
|
115
|
+
def test_assert_all
|
116
|
+
assert_efficient_sql :ALL => true do
|
117
|
+
Foo.find(:all)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# If your +WHERE+ and +ORDER+ clauses are too complex,
|
122
|
+
# MySQL might need to write a file (or worse), just to
|
123
|
+
# satisfy a query. +assert_efficient_sql+ detects this
|
124
|
+
# pernicious situation:
|
125
|
+
#
|
126
|
+
def test_prevent_filesorts
|
127
|
+
_exec %[ CREATE TABLE `t1` (
|
128
|
+
`a` int(11) NOT NULL DEFAULT '0',
|
129
|
+
`b` blob NOT NULL,
|
130
|
+
`c` text NOT NULL,
|
131
|
+
PRIMARY KEY (`a`,`b`(255),`c`(255)),
|
132
|
+
KEY `t1ba` (`b`(10),`a`)
|
133
|
+
) ENGINE=InnoDB ]
|
134
|
+
|
135
|
+
assert_flunked /Using.filesort/ do
|
136
|
+
assert_efficient_sql do
|
137
|
+
Foo.find_by_sql('SELECT a FROM t1 ORDER BY b')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
_exec 'drop table t1' # ERGO if exist
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_ALL_with_possible_keys
|
145
|
+
assert_efficient_sql :ALL => true, :Using_filesort => true, :key => false do
|
146
|
+
Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# <code>assert_efficient_sql</code> calls
|
151
|
+
# <code>SHOW SESSION STATUS</code> before and after
|
152
|
+
# its sampled block. If you need to prevent an advanced
|
153
|
+
# pessimization, such as <code>Created_tmp_disk_tables</code>,
|
154
|
+
# pass <code>:Created_tmp_disk_tables => 0</code>. The
|
155
|
+
# assertion will compare difference in STATUS before
|
156
|
+
# and after calling its block. A difference greater
|
157
|
+
# than the allowed difference will trigger a fault.
|
158
|
+
#
|
159
|
+
# To test this, we simply detect a STATUS variable
|
160
|
+
# which is not a warning.
|
161
|
+
#
|
162
|
+
def test_declare_futile_war_on_Innodb_rows_read
|
163
|
+
assert_flunked /just.for.test.*
|
164
|
+
Innodb_rows_read/mx do
|
165
|
+
assert_efficient_sql :diagnostic => 'just for test!',
|
166
|
+
:Innodb_rows_read => 0 do
|
167
|
+
Foo.find(:all)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# You can also nest the assertion, to provide different
|
173
|
+
# options for different blocks. The assertion allows
|
174
|
+
# this because your test might also have some other
|
175
|
+
# reason to use blocks
|
176
|
+
#
|
177
|
+
def test_nest
|
178
|
+
outer_result = assert_efficient_sql do
|
179
|
+
inner_result = assert_efficient_sql :ALL => true do
|
180
|
+
Foo.find(:all)
|
181
|
+
end
|
182
|
+
|
183
|
+
assert_no_match /where/i, inner_result[0][0]
|
184
|
+
Foo.find(42)
|
185
|
+
end
|
186
|
+
assert_match /where/i, outer_result[0][0]
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_inspect_sql
|
190
|
+
inspections = inspect_sql do
|
191
|
+
Foo.find_by_sql('SELECT id FROM t2 ORDER BY c')
|
192
|
+
Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
|
193
|
+
Foo.find_by_sql('SELECT * FROM foos, t2')
|
194
|
+
end
|
195
|
+
assert{ inspections.length == 3 }
|
196
|
+
assert{ inspections[0][:statement] == 'SELECT id FROM t2 ORDER BY c' }
|
197
|
+
assert{ inspections[0][:possible_keys] == [[nil]] }
|
198
|
+
assert{ inspections[0][:extras] == [[:Using_filesort]] }
|
199
|
+
assert{ inspections[0][:types] == [:ALL] }
|
200
|
+
assert{ inspections[1][:tables] == [:t2] }
|
201
|
+
assert{ inspections[1][:possible_keys] == [[nil]] }
|
202
|
+
assert{ inspections[2][:tables] == [:t2, :foos] } # note the order is constrained!
|
203
|
+
assert{ inspections[2][:possible_keys] == [[nil], [nil]] }
|
204
|
+
assert{ inspections[1][:keys] == [nil] } # [:PRIMARY] }
|
205
|
+
assert{ inspections[2][:keys] == [nil, nil] }
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_inspect_sql_explanations
|
209
|
+
inspections = inspect_sql do
|
210
|
+
Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
|
211
|
+
Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
|
212
|
+
Foo.find_by_sql('SELECT * FROM foos, t2')
|
213
|
+
end
|
214
|
+
assert{ inspections.length == 3 }
|
215
|
+
assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
|
216
|
+
assert{ inspections[0][:explanations].first[:possible_keys] == nil }
|
217
|
+
assert{ inspections[0][:explanations].first[:Extra] == 'Using_filesort' }
|
218
|
+
assert{ inspections[0][:explanations].first[:type] == 'ALL' }
|
219
|
+
assert{ inspections[1][:explanations].first[:table] == 't2' }
|
220
|
+
assert{ inspections[1][:explanations].first[:possible_keys] == nil }
|
221
|
+
assert{ inspections[2][:explanations].first[:table] == 't2' } # note the order is constrained!
|
222
|
+
assert{ inspections[1][:explanations].first[:possible_keys] == nil }
|
223
|
+
assert{ inspections[2][:explanations].first[:possible_keys] == nil }
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_inspect_sql_find_statements
|
227
|
+
inspections = inspect_sql do
|
228
|
+
Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
|
229
|
+
Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
|
230
|
+
Foo.find_by_sql('SELECT * FROM foos, t2')
|
231
|
+
end
|
232
|
+
inspections = inspections.find_statements(/from t2/i)
|
233
|
+
assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
|
234
|
+
assert{ inspections[1][:explanations].first[:table] == 't2' }
|
235
|
+
deny{ inspections[2] }
|
236
|
+
assert{ inspections.tables == [:t2] }
|
237
|
+
#assert{ inspections.keys.to_set == [nil, :PRIMARY].to_set }
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_inspect_certain_sql_statements
|
241
|
+
inspections = inspect_sql :match => /from t2/i do
|
242
|
+
Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
|
243
|
+
Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
|
244
|
+
Foo.find_by_sql('SELECT * FROM foos, t2')
|
245
|
+
end
|
246
|
+
assert{ inspections[0][:statement] == 'SELECT a FROM t2 ORDER BY c' }
|
247
|
+
assert{ inspections[1][:explanations].first[:table] == 't2' }
|
248
|
+
deny{ inspections[2] }
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_inspect_sql_rollups
|
252
|
+
inspections = inspect_sql :verbose => false do
|
253
|
+
Foo.find_by_sql('SELECT a FROM t2 ORDER BY c')
|
254
|
+
Foo.find_by_sql('SELECT c FROM t2 ORDER BY a')
|
255
|
+
Foo.find_by_sql('SELECT * FROM foos, t2')
|
256
|
+
end
|
257
|
+
assert{ inspections.tables.to_set == [:t2, :foos].to_set }
|
258
|
+
assert{ inspections.keys.to_set == [nil].to_set }
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_assert_latest
|
262
|
+
assert_flunked(/false/){ assert false }
|
263
|
+
|
264
|
+
assert_flunked /should create new Foo record\(s\) in/ do
|
265
|
+
assert_latest(Foo){ 'nada!' }
|
266
|
+
end
|
267
|
+
|
268
|
+
assert_flunked /because.*should not create new Foo record\(s\) in/m do
|
269
|
+
deny_latest Foo, 'because' do
|
270
|
+
Foo.create
|
271
|
+
end
|
272
|
+
end
|
273
|
+
nada = 42
|
274
|
+
|
275
|
+
assert_flunked /in block:\n.*nada/m do
|
276
|
+
assert_latest(Foo){ nada }
|
277
|
+
end
|
278
|
+
|
279
|
+
deny_flunked /42/ do # assert_latest shall not evaluate thy block
|
280
|
+
assert_latest(Foo){ nada }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_assert_polygamous_latest
|
285
|
+
f, t = assert_latest Foo, T2, 'diagnostic' do
|
286
|
+
Foo.create
|
287
|
+
T2.create(:c => 42)
|
288
|
+
end
|
289
|
+
|
290
|
+
assert{ f.id > 0 and t.id > 0 }
|
291
|
+
|
292
|
+
f1, f2 = assert_latest Foo do
|
293
|
+
2.times{ Foo.create }
|
294
|
+
end
|
295
|
+
|
296
|
+
assert 'items return ordered by id' do
|
297
|
+
f1.id > 0 and f2.id > f1.id
|
298
|
+
end
|
299
|
+
|
300
|
+
t, fz = assert_latest T2, Foo do
|
301
|
+
T2.create(:c => 42)
|
302
|
+
2.times{ Foo.create }
|
303
|
+
end
|
304
|
+
|
305
|
+
assert do
|
306
|
+
t.id > 0 and
|
307
|
+
fz[0].id > 0 and
|
308
|
+
fz[1].id > fz[0].id
|
309
|
+
end
|
310
|
+
|
311
|
+
t, (f1, f2) = assert_latest T2, Foo do
|
312
|
+
T2.create(:c => 42)
|
313
|
+
2.times{ Foo.create }
|
314
|
+
end
|
315
|
+
|
316
|
+
assert do
|
317
|
+
t.id > 0 and
|
318
|
+
f1.id > 0 and
|
319
|
+
f2.id > f1.id
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
def test_assert_polygamous_latest_fails
|
325
|
+
assert_flunked /diagnostic.*new T2 rcord/m do
|
326
|
+
f, t = assert_latest Foo, T2, 'diagnostic' do
|
327
|
+
Foo.create
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_deny_polygamous_latest_fails
|
333
|
+
assert_flunked /diagnostic.*new Foo record/m do
|
334
|
+
f, t = deny_latest Foo, T2, 'diagnostic' do
|
335
|
+
Foo.create
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def teardown #:nodoc:
|
341
|
+
ActiveRecord::Base.connection.drop_table(:foos) rescue nil
|
342
|
+
ActiveRecord::Base.connection.drop_table(:t2) rescue nil
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
#:enddoc:
|
348
|
+
|
349
|
+
class AssertNonMysqlTest < Test::Unit::TestCase
|
350
|
+
|
351
|
+
# The assertion requires MySQL. To peacefully coexist with
|
352
|
+
# test rigs that use multiple adapters, we only warn, once,
|
353
|
+
# if MySQL is not found. If you don't need this warning,
|
354
|
+
# turn it off with :warn_mysql_required => false
|
355
|
+
#
|
356
|
+
def test_gently_forwarn_non_mysql_connectors
|
357
|
+
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3',
|
358
|
+
:dbfile => ':memory:' )
|
359
|
+
|
360
|
+
deny_stdout /requires MySQL/, 'disabled warning' do
|
361
|
+
assert_efficient_sql :warn_mysql_required => false
|
362
|
+
end
|
363
|
+
|
364
|
+
assert_stdout /requires MySQL/, 'warn the first time' do
|
365
|
+
assert_efficient_sql
|
366
|
+
end
|
367
|
+
|
368
|
+
deny_stdout /requires MySQL/, 'don\'t warn the second time' do
|
369
|
+
assert_efficient_sql
|
370
|
+
end
|
371
|
+
|
372
|
+
ensure
|
373
|
+
config_file = (Assert_efficient_sql_root + 'config/database.yml').to_s
|
374
|
+
config = YAML.load(ERB.new(IO.read(config_file)).result)['test']
|
375
|
+
ActiveRecord::Base.establish_connection(config)
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
def rails_root # ERGO is this used?
|
382
|
+
return (Pathname.new(__FILE__).expand_path.dirname + '../../../..').expand_path
|
383
|
+
end
|
384
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: assert_efficient_sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phlip
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-09-08 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: assert2
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -22,7 +23,8 @@ dependencies:
|
|
22
23
|
version: "0"
|
23
24
|
version:
|
24
25
|
- !ruby/object:Gem::Dependency
|
25
|
-
name:
|
26
|
+
name: rubynode
|
27
|
+
type: :runtime
|
26
28
|
version_requirement:
|
27
29
|
version_requirements: !ruby/object:Gem::Requirement
|
28
30
|
requirements:
|
@@ -39,6 +41,7 @@ extensions: []
|
|
39
41
|
extra_rdoc_files: []
|
40
42
|
|
41
43
|
files:
|
44
|
+
- test/assert_efficient_sql_test.rb
|
42
45
|
- lib/assert_efficient_sql.rb
|
43
46
|
has_rdoc: false
|
44
47
|
homepage: http://rubyforge.org/projects/efficient-sql
|
@@ -62,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
65
|
requirements: []
|
63
66
|
|
64
67
|
rubyforge_project: efficient-sql
|
65
|
-
rubygems_version: 1.0
|
68
|
+
rubygems_version: 1.2.0
|
66
69
|
signing_key:
|
67
70
|
specification_version: 2
|
68
71
|
summary: efficient assertions for ActiveRecord tests
|