activerecord-refined 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/.yardopts +17 -0
- data/README.md +93 -965
- data/activerecord-refined.gemspec +12 -7
- data/docs/conditions.md +206 -0
- data/docs/ctes.md +65 -0
- data/docs/expressions.md +125 -0
- data/docs/functions.md +219 -0
- data/docs/grouping.md +55 -0
- data/docs/joins.md +73 -0
- data/docs/json.md +230 -0
- data/docs/ordering.md +55 -0
- data/docs/time_zones.md +30 -0
- data/docs/windows.md +41 -0
- data/docs/writing.md +33 -0
- data/examples/aggregations.rb +18 -0
- data/examples/expressions.rb +35 -5
- data/lib/active_record/refined/ast.rb +461 -246
- data/lib/active_record/refined/dialect/mariadb.rb +25 -0
- data/lib/active_record/refined/dialect/mysql.rb +18 -0
- data/lib/active_record/refined/dialect/mysql_compat.rb +67 -0
- data/lib/active_record/refined/dialect/oracle.rb +110 -0
- data/lib/active_record/refined/dialect/postgresql.rb +120 -0
- data/lib/active_record/refined/dialect/sql_server.rb +115 -0
- data/lib/active_record/refined/dialect/sqlite.rb +57 -0
- data/lib/active_record/refined/dialect.rb +340 -0
- data/lib/active_record/refined.rb +682 -192
- data/lib/activerecord-refined/version.rb +1 -1
- data/lib/activerecord-refined.rb +1 -0
- metadata +58 -16
- data/.github/workflows/push_gem.yml +0 -45
- data/.github/workflows/sandbox.yml +0 -295
- data/.github/workflows/test.yml +0 -104
- data/.gitignore +0 -19
- data/.rubocop.yml +0 -393
- data/Gemfile +0 -14
- data/Rakefile +0 -53
- data/benchmark/query_building.rb +0 -129
- data/test/test_block_syntax.rb +0 -2999
- data/test/test_helper.rb +0 -238
data/.rubocop.yml
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
plugins:
|
|
2
|
-
- rubocop-minitest
|
|
3
|
-
- rubocop-packaging
|
|
4
|
-
- rubocop-performance
|
|
5
|
-
- rubocop-rails
|
|
6
|
-
- rubocop-md
|
|
7
|
-
|
|
8
|
-
AllCops:
|
|
9
|
-
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
|
|
10
|
-
# to ignore them, so only the ones explicitly set in this file are enabled.
|
|
11
|
-
DisabledByDefault: true
|
|
12
|
-
SuggestExtensions: false
|
|
13
|
-
# Inferring from the gemspec would stumble over 4.1.0.dev, so say it plainly.
|
|
14
|
-
TargetRubyVersion: 4.1
|
|
15
|
-
Exclude:
|
|
16
|
-
- "**/tmp/**/*"
|
|
17
|
-
- "**/vendor/**/*"
|
|
18
|
-
- "**/node_modules/**/*"
|
|
19
|
-
- "pkg/**/*"
|
|
20
|
-
# A site rather than the library, and partly vendored from wasmify-rails.
|
|
21
|
-
- "sandbox/**/*"
|
|
22
|
-
|
|
23
|
-
Performance:
|
|
24
|
-
Exclude:
|
|
25
|
-
- '**/test/**/*'
|
|
26
|
-
|
|
27
|
-
Rails/IndexBy:
|
|
28
|
-
Enabled: true
|
|
29
|
-
|
|
30
|
-
Rails/IndexWith:
|
|
31
|
-
Enabled: true
|
|
32
|
-
|
|
33
|
-
# Prefer &&/|| over and/or.
|
|
34
|
-
Style/AndOr:
|
|
35
|
-
Enabled: true
|
|
36
|
-
|
|
37
|
-
Layout/ClosingHeredocIndentation:
|
|
38
|
-
Enabled: true
|
|
39
|
-
|
|
40
|
-
Layout/ClosingParenthesisIndentation:
|
|
41
|
-
Enabled: true
|
|
42
|
-
|
|
43
|
-
# Align comments with method definitions.
|
|
44
|
-
Layout/CommentIndentation:
|
|
45
|
-
Enabled: true
|
|
46
|
-
|
|
47
|
-
Layout/DefEndAlignment:
|
|
48
|
-
Enabled: true
|
|
49
|
-
|
|
50
|
-
Layout/ElseAlignment:
|
|
51
|
-
Enabled: true
|
|
52
|
-
|
|
53
|
-
# Align `end` with the matching keyword or starting expression except for
|
|
54
|
-
# assignments, where it should be aligned with the LHS.
|
|
55
|
-
Layout/EndAlignment:
|
|
56
|
-
Enabled: true
|
|
57
|
-
EnforcedStyleAlignWith: variable
|
|
58
|
-
AutoCorrect: true
|
|
59
|
-
|
|
60
|
-
Layout/EndOfLine:
|
|
61
|
-
Enabled: true
|
|
62
|
-
|
|
63
|
-
Layout/EmptyLineAfterMagicComment:
|
|
64
|
-
Enabled: true
|
|
65
|
-
|
|
66
|
-
Layout/EmptyLinesAroundAccessModifier:
|
|
67
|
-
Enabled: true
|
|
68
|
-
EnforcedStyle: only_before
|
|
69
|
-
|
|
70
|
-
Layout/EmptyLinesAroundBlockBody:
|
|
71
|
-
Enabled: true
|
|
72
|
-
|
|
73
|
-
# In a regular class definition, no empty lines around the body.
|
|
74
|
-
Layout/EmptyLinesAroundClassBody:
|
|
75
|
-
Enabled: true
|
|
76
|
-
|
|
77
|
-
# In a regular method definition, no empty lines around the body.
|
|
78
|
-
Layout/EmptyLinesAroundMethodBody:
|
|
79
|
-
Enabled: true
|
|
80
|
-
|
|
81
|
-
# In a regular module definition, no empty lines around the body.
|
|
82
|
-
Layout/EmptyLinesAroundModuleBody:
|
|
83
|
-
Enabled: true
|
|
84
|
-
|
|
85
|
-
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
|
86
|
-
Style/HashSyntax:
|
|
87
|
-
Enabled: true
|
|
88
|
-
EnforcedShorthandSyntax: either
|
|
89
|
-
|
|
90
|
-
# Method definitions after `private` or `protected` isolated calls need one
|
|
91
|
-
# extra level of indentation.
|
|
92
|
-
Layout/IndentationConsistency:
|
|
93
|
-
Enabled: true
|
|
94
|
-
EnforcedStyle: indented_internal_methods
|
|
95
|
-
Exclude:
|
|
96
|
-
- '**/*.md'
|
|
97
|
-
|
|
98
|
-
# Two spaces, no tabs (for indentation).
|
|
99
|
-
Layout/IndentationWidth:
|
|
100
|
-
Enabled: true
|
|
101
|
-
|
|
102
|
-
Layout/LeadingCommentSpace:
|
|
103
|
-
Enabled: true
|
|
104
|
-
|
|
105
|
-
Layout/SpaceAfterColon:
|
|
106
|
-
Enabled: true
|
|
107
|
-
|
|
108
|
-
Layout/SpaceAfterComma:
|
|
109
|
-
Enabled: true
|
|
110
|
-
|
|
111
|
-
Layout/SpaceAfterSemicolon:
|
|
112
|
-
Enabled: true
|
|
113
|
-
|
|
114
|
-
Layout/SpaceAroundEqualsInParameterDefault:
|
|
115
|
-
Enabled: true
|
|
116
|
-
|
|
117
|
-
Layout/SpaceAroundKeyword:
|
|
118
|
-
Enabled: true
|
|
119
|
-
|
|
120
|
-
Layout/SpaceAroundOperators:
|
|
121
|
-
Enabled: true
|
|
122
|
-
|
|
123
|
-
Layout/SpaceBeforeComma:
|
|
124
|
-
Enabled: true
|
|
125
|
-
|
|
126
|
-
Layout/SpaceBeforeComment:
|
|
127
|
-
Enabled: true
|
|
128
|
-
|
|
129
|
-
Layout/SpaceBeforeFirstArg:
|
|
130
|
-
Enabled: true
|
|
131
|
-
|
|
132
|
-
Style/DefWithParentheses:
|
|
133
|
-
Enabled: true
|
|
134
|
-
|
|
135
|
-
# Defining a method with parameters needs parentheses.
|
|
136
|
-
Style/MethodDefParentheses:
|
|
137
|
-
Enabled: true
|
|
138
|
-
|
|
139
|
-
Style/ExplicitBlockArgument:
|
|
140
|
-
Enabled: true
|
|
141
|
-
|
|
142
|
-
Style/FrozenStringLiteralComment:
|
|
143
|
-
Enabled: true
|
|
144
|
-
EnforcedStyle: always
|
|
145
|
-
Exclude:
|
|
146
|
-
- '**/*.md'
|
|
147
|
-
|
|
148
|
-
Style/MutableConstant:
|
|
149
|
-
Enabled: true
|
|
150
|
-
EnforcedStyle: literals
|
|
151
|
-
Exclude:
|
|
152
|
-
- '**/*.md'
|
|
153
|
-
|
|
154
|
-
Style/MapToHash:
|
|
155
|
-
Enabled: true
|
|
156
|
-
|
|
157
|
-
Style/RedundantFreeze:
|
|
158
|
-
Enabled: true
|
|
159
|
-
|
|
160
|
-
# Use `foo {}` not `foo{}`.
|
|
161
|
-
Layout/SpaceBeforeBlockBraces:
|
|
162
|
-
Enabled: true
|
|
163
|
-
|
|
164
|
-
# Use `foo { bar }` not `foo {bar}`.
|
|
165
|
-
Layout/SpaceInsideBlockBraces:
|
|
166
|
-
Enabled: true
|
|
167
|
-
EnforcedStyleForEmptyBraces: space
|
|
168
|
-
|
|
169
|
-
# Use `{ a: 1 }` not `{a:1}`.
|
|
170
|
-
Layout/SpaceInsideHashLiteralBraces:
|
|
171
|
-
Enabled: true
|
|
172
|
-
|
|
173
|
-
Layout/SpaceInsideParens:
|
|
174
|
-
Enabled: true
|
|
175
|
-
|
|
176
|
-
# Check quotes usage according to lint rule below.
|
|
177
|
-
Style/StringLiterals:
|
|
178
|
-
Enabled: true
|
|
179
|
-
EnforcedStyle: double_quotes
|
|
180
|
-
|
|
181
|
-
# Detect hard tabs, no hard tabs.
|
|
182
|
-
Layout/IndentationStyle:
|
|
183
|
-
Enabled: true
|
|
184
|
-
|
|
185
|
-
# Empty lines should not have any spaces.
|
|
186
|
-
Layout/TrailingEmptyLines:
|
|
187
|
-
Enabled: true
|
|
188
|
-
|
|
189
|
-
# No trailing whitespace.
|
|
190
|
-
Layout/TrailingWhitespace:
|
|
191
|
-
Enabled: true
|
|
192
|
-
|
|
193
|
-
# Use quotes for string literals when they are enough.
|
|
194
|
-
Style/RedundantPercentQ:
|
|
195
|
-
Enabled: true
|
|
196
|
-
|
|
197
|
-
Lint/NestedMethodDefinition:
|
|
198
|
-
Enabled: true
|
|
199
|
-
|
|
200
|
-
Lint/AmbiguousOperator:
|
|
201
|
-
Enabled: true
|
|
202
|
-
|
|
203
|
-
Lint/AmbiguousRegexpLiteral:
|
|
204
|
-
Enabled: true
|
|
205
|
-
|
|
206
|
-
Lint/Debugger:
|
|
207
|
-
Enabled: true
|
|
208
|
-
DebuggerRequires:
|
|
209
|
-
- debug
|
|
210
|
-
|
|
211
|
-
Lint/DuplicateRequire:
|
|
212
|
-
Enabled: true
|
|
213
|
-
|
|
214
|
-
Lint/DuplicateMagicComment:
|
|
215
|
-
Enabled: true
|
|
216
|
-
|
|
217
|
-
Lint/DuplicateMethods:
|
|
218
|
-
Enabled: true
|
|
219
|
-
|
|
220
|
-
Lint/ErbNewArguments:
|
|
221
|
-
Enabled: true
|
|
222
|
-
|
|
223
|
-
Lint/EnsureReturn:
|
|
224
|
-
Enabled: true
|
|
225
|
-
|
|
226
|
-
Lint/MissingCopEnableDirective:
|
|
227
|
-
Enabled: true
|
|
228
|
-
|
|
229
|
-
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
|
230
|
-
Lint/RequireParentheses:
|
|
231
|
-
Enabled: true
|
|
232
|
-
|
|
233
|
-
Lint/RedundantCopDisableDirective:
|
|
234
|
-
Enabled: true
|
|
235
|
-
|
|
236
|
-
Lint/RedundantCopEnableDirective:
|
|
237
|
-
Enabled: true
|
|
238
|
-
|
|
239
|
-
Lint/RedundantRequireStatement:
|
|
240
|
-
Enabled: true
|
|
241
|
-
|
|
242
|
-
Lint/RedundantStringCoercion:
|
|
243
|
-
Enabled: true
|
|
244
|
-
|
|
245
|
-
Lint/RedundantSafeNavigation:
|
|
246
|
-
Enabled: true
|
|
247
|
-
|
|
248
|
-
Lint/UriEscapeUnescape:
|
|
249
|
-
Enabled: true
|
|
250
|
-
|
|
251
|
-
Lint/UselessAssignment:
|
|
252
|
-
Enabled: true
|
|
253
|
-
|
|
254
|
-
Lint/DeprecatedClassMethods:
|
|
255
|
-
Enabled: true
|
|
256
|
-
|
|
257
|
-
Lint/InterpolationCheck:
|
|
258
|
-
Enabled: true
|
|
259
|
-
Exclude:
|
|
260
|
-
- '**/test/**/*'
|
|
261
|
-
|
|
262
|
-
Lint/SafeNavigationChain:
|
|
263
|
-
Enabled: true
|
|
264
|
-
|
|
265
|
-
Style/EvalWithLocation:
|
|
266
|
-
Enabled: true
|
|
267
|
-
Exclude:
|
|
268
|
-
- '**/test/**/*'
|
|
269
|
-
|
|
270
|
-
Style/ParenthesesAroundCondition:
|
|
271
|
-
Enabled: true
|
|
272
|
-
|
|
273
|
-
Style/HashTransformKeys:
|
|
274
|
-
Enabled: true
|
|
275
|
-
|
|
276
|
-
Style/HashTransformValues:
|
|
277
|
-
Enabled: true
|
|
278
|
-
|
|
279
|
-
Style/RedundantBegin:
|
|
280
|
-
Enabled: true
|
|
281
|
-
|
|
282
|
-
Style/RedundantReturn:
|
|
283
|
-
Enabled: true
|
|
284
|
-
AllowMultipleReturnValues: true
|
|
285
|
-
|
|
286
|
-
Style/RedundantRegexpEscape:
|
|
287
|
-
Enabled: true
|
|
288
|
-
|
|
289
|
-
Style/Semicolon:
|
|
290
|
-
Enabled: true
|
|
291
|
-
AllowAsExpressionSeparator: true
|
|
292
|
-
|
|
293
|
-
# Prefer Foo.method over Foo::method
|
|
294
|
-
Style/ColonMethodCall:
|
|
295
|
-
Enabled: true
|
|
296
|
-
|
|
297
|
-
Style/TrivialAccessors:
|
|
298
|
-
Enabled: true
|
|
299
|
-
|
|
300
|
-
# Prefer a = b || c over a = b ? b : c
|
|
301
|
-
Style/RedundantCondition:
|
|
302
|
-
Enabled: true
|
|
303
|
-
|
|
304
|
-
Style/RedundantDoubleSplatHashBraces:
|
|
305
|
-
Enabled: true
|
|
306
|
-
|
|
307
|
-
Style/OpenStructUse:
|
|
308
|
-
Enabled: true
|
|
309
|
-
|
|
310
|
-
Style/ArrayIntersect:
|
|
311
|
-
Enabled: true
|
|
312
|
-
|
|
313
|
-
Style/KeywordArgumentsMerging:
|
|
314
|
-
Enabled: true
|
|
315
|
-
|
|
316
|
-
Performance/BindCall:
|
|
317
|
-
Enabled: true
|
|
318
|
-
|
|
319
|
-
Performance/FlatMap:
|
|
320
|
-
Enabled: true
|
|
321
|
-
|
|
322
|
-
Performance/MapCompact:
|
|
323
|
-
Enabled: true
|
|
324
|
-
|
|
325
|
-
# Off, unlike Rails: select {}.map {} here is almost always Active Record's
|
|
326
|
-
# select -- the query DSL -- and filter_map would break it, which the cop
|
|
327
|
-
# cannot tell from Enumerable's.
|
|
328
|
-
Performance/SelectMap:
|
|
329
|
-
Enabled: false
|
|
330
|
-
|
|
331
|
-
Performance/RedundantMerge:
|
|
332
|
-
Enabled: true
|
|
333
|
-
|
|
334
|
-
Performance/StartWith:
|
|
335
|
-
Enabled: true
|
|
336
|
-
|
|
337
|
-
Performance/EndWith:
|
|
338
|
-
Enabled: true
|
|
339
|
-
|
|
340
|
-
Performance/RegexpMatch:
|
|
341
|
-
Enabled: true
|
|
342
|
-
|
|
343
|
-
Performance/ReverseEach:
|
|
344
|
-
Enabled: true
|
|
345
|
-
|
|
346
|
-
Performance/StringReplacement:
|
|
347
|
-
Enabled: true
|
|
348
|
-
|
|
349
|
-
Performance/DeletePrefix:
|
|
350
|
-
Enabled: true
|
|
351
|
-
|
|
352
|
-
Performance/DeleteSuffix:
|
|
353
|
-
Enabled: true
|
|
354
|
-
|
|
355
|
-
Performance/InefficientHashSearch:
|
|
356
|
-
Enabled: true
|
|
357
|
-
|
|
358
|
-
Performance/ConstantRegexp:
|
|
359
|
-
Enabled: true
|
|
360
|
-
|
|
361
|
-
Performance/RedundantStringChars:
|
|
362
|
-
Enabled: true
|
|
363
|
-
|
|
364
|
-
Performance/StringInclude:
|
|
365
|
-
Enabled: true
|
|
366
|
-
|
|
367
|
-
Minitest/AssertNil:
|
|
368
|
-
Enabled: true
|
|
369
|
-
|
|
370
|
-
Minitest/AssertRaisesWithRegexpArgument:
|
|
371
|
-
Enabled: true
|
|
372
|
-
|
|
373
|
-
Minitest/AssertWithExpectedArgument:
|
|
374
|
-
Enabled: true
|
|
375
|
-
|
|
376
|
-
Minitest/LiteralAsActualArgument:
|
|
377
|
-
Enabled: true
|
|
378
|
-
|
|
379
|
-
Minitest/NonExecutableTestMethod:
|
|
380
|
-
Enabled: true
|
|
381
|
-
|
|
382
|
-
Minitest/SkipEnsure:
|
|
383
|
-
Enabled: true
|
|
384
|
-
|
|
385
|
-
Minitest/UnreachableAssertion:
|
|
386
|
-
Enabled: true
|
|
387
|
-
|
|
388
|
-
Markdown:
|
|
389
|
-
# Off, unlike Rails: README abbreviates a snippet to its shape --
|
|
390
|
-
# with_recursive(tree: [...]) -- which is not Ruby and means not to be.
|
|
391
|
-
WarnInvalid: false
|
|
392
|
-
# Whether to lint codeblocks without code attributes
|
|
393
|
-
Autodetect: false
|
data/Gemfile
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
source "https://rubygems.org"
|
|
4
|
-
|
|
5
|
-
# Specify your gem's dependencies in activerecord-refined.gemspec
|
|
6
|
-
gemspec
|
|
7
|
-
|
|
8
|
-
# Only ADAPTER=postgresql and ADAPTER=mysql2 need these, and building them
|
|
9
|
-
# needs the client libraries installed. `rake test` runs on SQLite, so a
|
|
10
|
-
# checkout without them is still usable: bundle config set --local without db
|
|
11
|
-
group :db do
|
|
12
|
-
gem "mysql2"
|
|
13
|
-
gem "pg"
|
|
14
|
-
end
|
data/Rakefile
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/gem_tasks"
|
|
4
|
-
require "rake/testtask"
|
|
5
|
-
require "socket"
|
|
6
|
-
|
|
7
|
-
ADAPTERS = %w[sqlite3 postgresql mysql2].freeze
|
|
8
|
-
|
|
9
|
-
# The devcontainer serves Oracle's MySQL beside MariaDB, on 3307. A
|
|
10
|
-
# container from before it existed serves nothing there, and test:all says
|
|
11
|
-
# so rather than failing or keeping quiet.
|
|
12
|
-
def mysql8_reachable?
|
|
13
|
-
Socket.tcp("127.0.0.1", 3307, connect_timeout: 1) { true }
|
|
14
|
-
rescue SystemCallError
|
|
15
|
-
false
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
Rake::TestTask.new do |t|
|
|
19
|
-
t.test_files = FileList["test/test_*.rb"]
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
namespace :test do
|
|
23
|
-
ADAPTERS.each do |adapter|
|
|
24
|
-
desc "Run the tests against #{adapter}"
|
|
25
|
-
task adapter do
|
|
26
|
-
puts "==== #{adapter} ===="
|
|
27
|
-
ENV["ADAPTER"] = adapter
|
|
28
|
-
ENV.delete("DB_PORT")
|
|
29
|
-
Rake::Task[:test].reenable
|
|
30
|
-
Rake::Task[:test].invoke
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
desc "Run the tests against MySQL, which the devcontainer serves on 3307"
|
|
35
|
-
task :mysql8 do
|
|
36
|
-
puts "==== mysql2 (MySQL, port 3307) ===="
|
|
37
|
-
ENV["ADAPTER"] = "mysql2"
|
|
38
|
-
ENV["DB_PORT"] = "3307"
|
|
39
|
-
Rake::Task[:test].reenable
|
|
40
|
-
Rake::Task[:test].invoke
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
desc "Run the tests against every adapter in turn"
|
|
44
|
-
task all: ADAPTERS do
|
|
45
|
-
if mysql8_reachable?
|
|
46
|
-
Rake::Task["test:mysql8"].invoke
|
|
47
|
-
else
|
|
48
|
-
puts "MySQL is not listening on 3307; skipped"
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
task default: :test
|
data/benchmark/query_building.rb
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Compares the cost of building the same queries through this gem's block
|
|
4
|
-
# DSL and through Active Record's other argument styles: hash conditions,
|
|
5
|
-
# string conditions, raw Arel, and relation and/or chains. Only query
|
|
6
|
-
# construction (through to_sql) is measured; every style produces the same
|
|
7
|
-
# SQL, which the script prints first as a sanity check.
|
|
8
|
-
#
|
|
9
|
-
# The profiling gems are development dependencies, so:
|
|
10
|
-
#
|
|
11
|
-
# bundle exec ruby benchmark/query_building.rb
|
|
12
|
-
|
|
13
|
-
require "benchmark/ips"
|
|
14
|
-
require "memory_profiler"
|
|
15
|
-
require "active_record"
|
|
16
|
-
require "activerecord-refined"
|
|
17
|
-
|
|
18
|
-
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
19
|
-
ActiveRecord::Schema.verbose = false
|
|
20
|
-
ActiveRecord::Schema.define do
|
|
21
|
-
create_table(:users) { |t| t.string :name; t.integer :age }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
class User < ActiveRecord::Base; end
|
|
25
|
-
|
|
26
|
-
T = User.arel_table
|
|
27
|
-
|
|
28
|
-
# Each variant must generate equivalent SQL; sanity-print once.
|
|
29
|
-
VARIANTS = {
|
|
30
|
-
"simple equality" => {
|
|
31
|
-
"hash" => -> { User.where(name: "alice").to_sql },
|
|
32
|
-
"string" => -> { User.where("name = ?", "alice").to_sql },
|
|
33
|
-
"arel" => -> { User.where(T[:name].eq("alice")).to_sql },
|
|
34
|
-
"block" => -> { User.where { :name == "alice" }.to_sql },
|
|
35
|
-
},
|
|
36
|
-
"range (BETWEEN)" => {
|
|
37
|
-
"hash" => -> { User.where(age: 20..40).to_sql },
|
|
38
|
-
"arel" => -> { User.where(T[:age].between(20..40)).to_sql },
|
|
39
|
-
"block" => -> { User.where { :age.in?(20..40) }.to_sql },
|
|
40
|
-
},
|
|
41
|
-
"LIKE" => {
|
|
42
|
-
"string" => -> { User.where("name LIKE ?", "ma%").to_sql },
|
|
43
|
-
"arel" => -> { User.where(T[:name].matches("ma%", nil, true)).to_sql },
|
|
44
|
-
"block" => -> { User.where { :name.like?("al%") }.to_sql },
|
|
45
|
-
},
|
|
46
|
-
"compound AND/OR" => {
|
|
47
|
-
"string" => -> { User.where("age >= ? AND (name = ? OR name = ?)", 18, "alice", "bob").to_sql },
|
|
48
|
-
"arel" => -> { User.where(T[:age].gteq(18).and(T[:name].eq("alice").or(T[:name].eq("bob")))).to_sql },
|
|
49
|
-
"relation" => -> { User.where(age: 18..).and(User.where(name: "alice").or(User.where(name: "bob"))).to_sql },
|
|
50
|
-
"block" => -> { User.where { (:age >= 18) & ((:name == "alice") | (:name == "bob")) }.to_sql },
|
|
51
|
-
},
|
|
52
|
-
}.freeze
|
|
53
|
-
|
|
54
|
-
puts "=== generated SQL (sanity) ==="
|
|
55
|
-
VARIANTS.each do |group, variants|
|
|
56
|
-
puts "--- #{group} ---"
|
|
57
|
-
variants.each { |name, thunk| puts " #{name.ljust(8)} #{thunk.call}" }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
puts
|
|
61
|
-
puts "=== speed (queries built per second) ==="
|
|
62
|
-
VARIANTS.each do |group, variants|
|
|
63
|
-
puts "--- #{group} ---"
|
|
64
|
-
Benchmark.ips do |x|
|
|
65
|
-
x.config(warmup: 0.5, time: 2)
|
|
66
|
-
variants.each { |name, thunk| x.report(name, &thunk) }
|
|
67
|
-
x.compare!
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
puts
|
|
72
|
-
puts "=== memory (per single call) ==="
|
|
73
|
-
fmt = "%-18s %-9s %12s %12s"
|
|
74
|
-
puts format(fmt, "group", "variant", "allocated B", "objects")
|
|
75
|
-
VARIANTS.each do |group, variants|
|
|
76
|
-
variants.each do |name, thunk|
|
|
77
|
-
thunk.call # warm caches (schema, statement caches) outside the report
|
|
78
|
-
report = MemoryProfiler.report { thunk.call }
|
|
79
|
-
puts format(fmt, group, name, report.total_allocated_memsize, report.total_allocated)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
puts
|
|
84
|
-
puts "=== Proc#refined ISeq copy (memory) ==="
|
|
85
|
-
require "objspace"
|
|
86
|
-
|
|
87
|
-
# Proc#refined runs the block under the refinements by deep-copying its
|
|
88
|
-
# instruction sequence, nested blocks included. The copy is made lazily on
|
|
89
|
-
# the refined proc's first call and memoized per source iseq and refinement
|
|
90
|
-
# list for the life of the VM, so it is paid once per block call site, not
|
|
91
|
-
# per query.
|
|
92
|
-
iseq_count = -> {
|
|
93
|
-
counts = ObjectSpace.count_imemo_objects
|
|
94
|
-
counts[:imemo_iseq] || counts[:iseq]
|
|
95
|
-
}
|
|
96
|
-
context = ActiveRecord::Refined::BlockContext.new(User)
|
|
97
|
-
syntax = ActiveRecord::Refined::BlockSyntax
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
"simple equality" => proc { :name == "alice" },
|
|
101
|
-
"compound AND/OR" => proc { (:age >= 18) & ((:name == "alice") | (:name == "bob")) },
|
|
102
|
-
}.each do |label, blk|
|
|
103
|
-
refined = blk.refined(syntax)
|
|
104
|
-
context.instance_exec(&refined) # the copy is made here, on the first call
|
|
105
|
-
original = ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(blk))
|
|
106
|
-
copy = ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(refined))
|
|
107
|
-
puts "#{label}: original iseq #{original} B, refined copy #{copy} B"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
make_proc = -> { proc { :age > 20 } }
|
|
111
|
-
context.instance_exec(&make_proc.call.refined(syntax))
|
|
112
|
-
before = iseq_count.call
|
|
113
|
-
1000.times { context.instance_exec(&make_proc.call.refined(syntax)) }
|
|
114
|
-
puts "1000 more calls from the same call site copied #{iseq_count.call - before} iseqs"
|
|
115
|
-
|
|
116
|
-
puts
|
|
117
|
-
puts "=== where the block path spends its time ==="
|
|
118
|
-
block = proc { :name == "alice" }
|
|
119
|
-
refined_block = block.refined(ActiveRecord::Refined::BlockSyntax)
|
|
120
|
-
context = ActiveRecord::Refined::BlockContext.new(User)
|
|
121
|
-
Benchmark.ips do |x|
|
|
122
|
-
x.config(warmup: 0.5, time: 2)
|
|
123
|
-
x.report("Proc#refined alone") { block.refined(ActiveRecord::Refined::BlockSyntax) }
|
|
124
|
-
x.report("instance_exec of pre-refined proc") { context.instance_exec(&refined_block) }
|
|
125
|
-
x.report("refined + instance_exec") {
|
|
126
|
-
ActiveRecord::Refined::BlockContext.new(User).instance_exec(&block.refined(ActiveRecord::Refined::BlockSyntax))
|
|
127
|
-
}
|
|
128
|
-
x.compare!
|
|
129
|
-
end
|