assert_efficient_sql 0.3.0 → 0.3.1
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/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
|