activerecord-refined 0.3.1 → 0.3.3
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/.github/workflows/push_gem.yml +45 -0
- data/.github/workflows/test.yml +23 -0
- data/.gitignore +2 -0
- data/Gemfile +8 -0
- data/README.md +141 -8
- data/Rakefile +17 -0
- data/examples/aggregations.rb +91 -0
- data/examples/complex_joins.rb +69 -0
- data/lib/active_record/refined/ast.rb +275 -54
- data/lib/active_record/refined.rb +5 -7
- data/lib/activerecord-refined/version.rb +1 -1
- data/test/test_block_syntax.rb +370 -72
- data/test/test_helper.rb +94 -3
- metadata +5 -1
data/test/test_block_syntax.rb
CHANGED
|
@@ -2,31 +2,31 @@ require_relative 'test_helper'
|
|
|
2
2
|
|
|
3
3
|
class TestBlockSyntax < Minitest::Test
|
|
4
4
|
def test_equal
|
|
5
|
-
|
|
5
|
+
assert_sql(/WHERE "users"."name" = 'matz'/, User.where { :name == 'matz' }.to_sql)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def test_not_equal
|
|
9
|
-
|
|
9
|
+
assert_sql(/WHERE "users"."name" != 'nobu'/, User.where { :name != 'nobu' }.to_sql)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def test_greater_than
|
|
13
|
-
|
|
13
|
+
assert_sql(/WHERE "users"."age" > 3/, User.where { :age > 3 }.to_sql)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_greater_than_or_equal
|
|
17
|
-
|
|
17
|
+
assert_sql(/WHERE "users"."age" >= 18/, User.where { :age >= 18 }.to_sql)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def test_less_than
|
|
21
|
-
|
|
21
|
+
assert_sql(/WHERE "users"."age" < 60/, User.where { :age < 60 }.to_sql)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def test_less_than_or_equal
|
|
25
|
-
|
|
25
|
+
assert_sql(/WHERE "users"."age" <= 35/, User.where { :age <= 35 }.to_sql)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def test_like
|
|
29
|
-
|
|
29
|
+
assert_sql(/WHERE "users"."name" LIKE 'tender%'/, User.where { :name.like?('tender%') }.to_sql)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def test_outside_of_where_block
|
|
@@ -34,149 +34,432 @@ class TestBlockSyntax < Minitest::Test
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def test_and
|
|
37
|
-
|
|
37
|
+
assert_sql(/WHERE "users"."name" = 'matz' AND "users"."age" > 18/,
|
|
38
38
|
User.where { (:name == 'matz') & (:age > 18) }.to_sql)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def test_or
|
|
42
|
-
|
|
42
|
+
assert_sql(/WHERE \(?\"users\".\"name\" = 'matz' OR \"users\".\"name\" = 'nobu'\)?/,
|
|
43
43
|
User.where { (:name == 'matz') | (:name == 'nobu') }.to_sql)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def test_not
|
|
47
|
-
|
|
47
|
+
assert_sql(/WHERE NOT \(?\"users\".\"name\" = 'matz'\)?/,
|
|
48
48
|
User.where { !(:name == 'matz') }.to_sql)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def test_complex_combination
|
|
52
52
|
sql = User.where { ((:name == 'matz') & (:age > 18)) | !(:name == 'nobu') }.to_sql
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
assert_sql(/"users"."name" = 'matz'/, sql)
|
|
54
|
+
assert_sql(/"users"."age" > 18/, sql)
|
|
55
|
+
assert_sql(/NOT/, sql)
|
|
56
|
+
assert_sql(/OR/, sql)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
User.where { :
|
|
59
|
+
def test_like_qualified
|
|
60
|
+
assert_sql(/WHERE "users"."name" LIKE 'tender%'/,
|
|
61
|
+
User.where { :users[:name].like?('tender%') }.to_sql)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def test_not_like
|
|
65
|
-
|
|
66
|
-
User.where {
|
|
65
|
+
assert_sql(/WHERE NOT \("users"."name" LIKE 'tender%'\)/,
|
|
66
|
+
User.where { !:name.like?('tender%') }.to_sql)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_start_with
|
|
70
|
+
assert_sql(/WHERE "users"."name" LIKE 'tender%' ESCAPE '\\'/,
|
|
71
|
+
User.where { :name.start_with?('tender') }.to_sql)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_end_with
|
|
75
|
+
assert_sql(/WHERE "users"."name" LIKE '%love' ESCAPE '\\'/,
|
|
76
|
+
User.where { :name.end_with?('love') }.to_sql)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_include
|
|
80
|
+
assert_sql(/WHERE "users"."name" LIKE '%der%' ESCAPE '\\'/,
|
|
81
|
+
User.where { :name.include?('der') }.to_sql)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Like their String namesakes, start_with? and end_with? take any number
|
|
85
|
+
# of literals; matching any one of them is enough.
|
|
86
|
+
def test_start_with_multiple
|
|
87
|
+
assert_sql(
|
|
88
|
+
/WHERE \("users"."name" LIKE 'ma%' ESCAPE '\\' OR "users"."name" LIKE 'no%' ESCAPE '\\'\)/,
|
|
89
|
+
User.where { :name.start_with?('ma', 'no') }.to_sql)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_end_with_multiple
|
|
93
|
+
assert_sql(
|
|
94
|
+
/WHERE \("users"."name" LIKE '%z' ESCAPE '\\' OR "users"."name" LIKE '%love' ESCAPE '\\'\)/,
|
|
95
|
+
User.where { :name.end_with?('z', 'love') }.to_sql)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The OR arrives grouped, so a following & applies to the whole list.
|
|
99
|
+
def test_start_with_multiple_combined
|
|
100
|
+
assert_sql(
|
|
101
|
+
/WHERE \("users"."name" LIKE 'ma%' ESCAPE '\\' OR "users"."name" LIKE 'no%' ESCAPE '\\'\) AND "users"."age" > 18/,
|
|
102
|
+
User.where { :name.start_with?('ma', 'no') & (:age > 18) }.to_sql)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_start_with_no_arguments
|
|
106
|
+
assert_raises(ArgumentError) { User.where { :name.start_with? } }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_end_with_no_arguments
|
|
110
|
+
assert_raises(ArgumentError) { User.where { :name.end_with? } }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_start_with_escapes_wildcards
|
|
114
|
+
assert_sql(/WHERE "users"."name" LIKE '100\\%\\_%' ESCAPE '\\'/,
|
|
115
|
+
User.where { :name.start_with?('100%_') }.to_sql)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_include_escapes_wildcards
|
|
119
|
+
assert_sql(/WHERE "users"."name" LIKE '%100\\%%' ESCAPE '\\'/,
|
|
120
|
+
User.where { :name.include?('100%') }.to_sql)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_member
|
|
124
|
+
assert_sql(/WHERE "users"."tags" @> '\{ruby\}'/,
|
|
125
|
+
User.where { :tags.member?('ruby') }.to_sql)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_member_qualified
|
|
129
|
+
assert_sql(/WHERE "users"."tags" @> '\{ruby\}'/,
|
|
130
|
+
User.where { :users[:tags].member?('ruby') }.to_sql)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_member_negated
|
|
134
|
+
assert_sql(/WHERE NOT \("users"."tags" @> '\{ruby\}'\)/,
|
|
135
|
+
User.where { !:tags.member?('ruby') }.to_sql)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_member_multiple_elements
|
|
139
|
+
assert_sql(/WHERE "users"."tags" @> '\{ruby,rails\}'/,
|
|
140
|
+
User.where { :tags.member?(%w[ruby rails]) }.to_sql)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# MySQL additionally escapes the double quotes inside its string literal,
|
|
144
|
+
# so the exact spelling is only asserted where the operator is real.
|
|
145
|
+
def test_member_quotes_special_elements
|
|
146
|
+
skip_without_array_columns
|
|
147
|
+
assert_sql(/WHERE "users"."tags" @> '\{"with,comma"\}'/,
|
|
148
|
+
User.where { :tags.member?('with,comma') }.to_sql)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# include? is a substring match even on an array column; only member?
|
|
152
|
+
# means containment.
|
|
153
|
+
def test_include_is_like_even_on_array_columns
|
|
154
|
+
skip_without_array_columns
|
|
155
|
+
assert_sql(/WHERE "users"."tags" LIKE '%ruby%' ESCAPE '\\'/,
|
|
156
|
+
User.where { :tags.include?('ruby') }.to_sql)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Elements survive the trip through the array literal: % is an ordinary
|
|
160
|
+
# character there, a comma stays inside its element, and quotes and
|
|
161
|
+
# backslashes are escaped.
|
|
162
|
+
def test_member_matches_elements_literally
|
|
163
|
+
skip_without_array_columns
|
|
164
|
+
User.delete_all
|
|
165
|
+
User.create!(name: 'literal', tags: ['100%', 'with,comma', 'q"uote', 'back\\slash'])
|
|
166
|
+
User.create!(name: 'lookalike', tags: ['100200', 'with', 'comma'])
|
|
167
|
+
assert_equal(['literal'], User.where { :tags.member?('100%') }.pluck(:name))
|
|
168
|
+
assert_equal(['literal'], User.where { :tags.member?('with,comma') }.pluck(:name))
|
|
169
|
+
assert_equal(['literal'], User.where { :tags.member?('q"uote') }.pluck(:name))
|
|
170
|
+
assert_equal(['literal'], User.where { :tags.member?('back\\slash') }.pluck(:name))
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def test_regexp
|
|
174
|
+
skip_without_regexp_support
|
|
175
|
+
assert_sql(/WHERE "users"."name" #{regexp_operator} '\^ma'/,
|
|
176
|
+
User.where { :name =~ '^ma' }.to_sql)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_not_regexp
|
|
180
|
+
skip_without_regexp_support
|
|
181
|
+
assert_sql(/WHERE "users"."name" #{not_regexp_operator} '\^ma'/,
|
|
182
|
+
User.where { :name !~ '^ma' }.to_sql)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_regexp_qualified
|
|
186
|
+
skip_without_regexp_support
|
|
187
|
+
assert_sql(/WHERE "users"."name" #{regexp_operator} '\^ma'/,
|
|
188
|
+
User.where { :users[:name] =~ '^ma' }.to_sql)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_regexp_on_function
|
|
192
|
+
skip_without_regexp_support
|
|
193
|
+
assert_sql(/WHERE UPPER\("users"."name"\) #{regexp_operator} '\^MA'/,
|
|
194
|
+
User.where { upper(:name) =~ '^MA' }.to_sql)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_regexp_literal
|
|
198
|
+
skip_without_regexp_support
|
|
199
|
+
assert_sql(/WHERE "users"."name" #{regexp_operator} 'love\$'/,
|
|
200
|
+
User.where { :name =~ /love$/ }.to_sql)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Rejected while the block runs, so this holds on every adapter.
|
|
204
|
+
def test_regexp_literal_with_options_is_rejected
|
|
205
|
+
assert_raises(ArgumentError) { User.where { :name =~ /^ma/i } }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def test_between
|
|
209
|
+
assert_sql(/WHERE "users"."age" BETWEEN 18 AND 65/,
|
|
210
|
+
User.where { :age.between?(18, 65) }.to_sql)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_in_range
|
|
214
|
+
assert_sql(/WHERE "users"."age" BETWEEN 18 AND 65/,
|
|
215
|
+
User.where { :age.in?(18..65) }.to_sql)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def test_in_endless_range
|
|
219
|
+
assert_sql(/WHERE "users"."age" >= 18/,
|
|
220
|
+
User.where { :age.in?(18..) }.to_sql)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_in_exclusive_range
|
|
224
|
+
assert_sql(/WHERE "users"."age" >= 18 AND "users"."age" < 65/,
|
|
225
|
+
User.where { :age.in?(18...65) }.to_sql)
|
|
67
226
|
end
|
|
68
227
|
|
|
69
228
|
def test_not_between
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
User.where { :age != (18..65) }.to_sql)
|
|
229
|
+
assert_sql(/WHERE NOT \("users"."age" BETWEEN 18 AND 65\)/,
|
|
230
|
+
User.where { !:age.between?(18, 65) }.to_sql)
|
|
73
231
|
end
|
|
74
232
|
|
|
75
233
|
def test_is_null
|
|
76
|
-
|
|
234
|
+
assert_sql(/WHERE "users"."name" IS NULL/,
|
|
77
235
|
User.where { :name.null? }.to_sql)
|
|
78
236
|
end
|
|
79
237
|
|
|
80
238
|
def test_is_null_qualified
|
|
81
|
-
|
|
239
|
+
assert_sql(/WHERE "users"."name" IS NULL/,
|
|
82
240
|
User.where { :users[:name].null? }.to_sql)
|
|
83
241
|
end
|
|
84
242
|
|
|
243
|
+
def test_equal_nil_is_rejected
|
|
244
|
+
e = assert_raises(ArgumentError) { User.where { :name == nil } }
|
|
245
|
+
assert_match(/null\?/, e.message)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def test_not_equal_nil_is_rejected
|
|
249
|
+
e = assert_raises(ArgumentError) { User.where { :name != nil } }
|
|
250
|
+
assert_match(/null\?/, e.message)
|
|
251
|
+
end
|
|
252
|
+
|
|
85
253
|
def test_in
|
|
86
|
-
|
|
87
|
-
User.where { :age
|
|
254
|
+
assert_sql(/WHERE "users"."age" IN \(1, 2, 3\)/,
|
|
255
|
+
User.where { :age.in?([1, 2, 3]) }.to_sql)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def test_in_qualified
|
|
259
|
+
assert_sql(/WHERE "users"."age" IN \(1, 2, 3\)/,
|
|
260
|
+
User.where { :users[:age].in?([1, 2, 3]) }.to_sql)
|
|
88
261
|
end
|
|
89
262
|
|
|
90
263
|
def test_not_in
|
|
91
|
-
|
|
92
|
-
User.where {
|
|
264
|
+
assert_sql(/WHERE NOT \("users"."age" IN \(1, 2, 3\)\)/,
|
|
265
|
+
User.where { !:age.in?([1, 2, 3]) }.to_sql)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def test_in_subquery
|
|
269
|
+
assert_sql(
|
|
270
|
+
/WHERE "authors"."id" IN \(SELECT "posts"."author_id" FROM "posts" WHERE "posts"."title" = 'pub'\)/,
|
|
271
|
+
Author.where { :id.in?(Post.where(title: 'pub').select(:author_id)) }.to_sql)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# A relation without an explicit select list selects its primary key, the
|
|
275
|
+
# same way ActiveRecord's own where(id: relation) does.
|
|
276
|
+
def test_in_subquery_selects_primary_key_by_default
|
|
277
|
+
assert_sql(/WHERE "authors"."id" IN \(SELECT "posts"."id" FROM "posts"\)/,
|
|
278
|
+
Author.where { :id.in?(Post.all) }.to_sql)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def test_not_in_subquery
|
|
282
|
+
assert_sql(/WHERE NOT \("authors"."id" IN \(SELECT "posts"."author_id" FROM "posts"\)\)/,
|
|
283
|
+
Author.where { !:id.in?(Post.select(:author_id)) }.to_sql)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# The subquery correlates with the outer table through qualified columns,
|
|
287
|
+
# and its own where block goes through the DSL too.
|
|
288
|
+
def test_exists
|
|
289
|
+
assert_sql(
|
|
290
|
+
/WHERE EXISTS \(SELECT "posts"\.\* FROM "posts" WHERE "posts"."author_id" = "authors"."id"\)/,
|
|
291
|
+
Author.where { exists?(Post.where { :posts[:author_id] == :authors[:id] }) }.to_sql)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def test_not_exists
|
|
295
|
+
assert_sql(/WHERE NOT \(EXISTS \(SELECT "posts"\.\* FROM "posts"\)\)/,
|
|
296
|
+
Author.where { !exists?(Post.all) }.to_sql)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def test_exists_combined
|
|
300
|
+
assert_sql(
|
|
301
|
+
/WHERE "authors"."name" = 'matz' AND EXISTS \(SELECT "posts"\.\* FROM "posts" WHERE "posts"."title" = 'pub'\)/,
|
|
302
|
+
Author.where { (:name == 'matz') & exists?(Post.where(title: 'pub')) }.to_sql)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def test_exists_execution
|
|
306
|
+
Author.delete_all
|
|
307
|
+
Post.delete_all
|
|
308
|
+
with_post = Author.create!(name: 'with_post')
|
|
309
|
+
Author.create!(name: 'without')
|
|
310
|
+
Post.create!(title: 'pub', author_id: with_post.id)
|
|
311
|
+
correlated = -> { Post.where { :posts[:author_id] == :authors[:id] } }
|
|
312
|
+
assert_equal(['with_post'],
|
|
313
|
+
Author.where { exists?(correlated.call) }.pluck(:name))
|
|
314
|
+
assert_equal(['without'],
|
|
315
|
+
Author.where { !exists?(correlated.call) }.pluck(:name))
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def test_in_subquery_execution
|
|
319
|
+
Author.delete_all
|
|
320
|
+
Post.delete_all
|
|
321
|
+
published = Author.create!(name: 'published')
|
|
322
|
+
drafting = Author.create!(name: 'drafting')
|
|
323
|
+
Post.create!(title: 'pub', author_id: published.id)
|
|
324
|
+
Post.create!(title: 'draft', author_id: drafting.id)
|
|
325
|
+
subquery = -> { Post.where(title: 'pub').select(:author_id) }
|
|
326
|
+
assert_equal(['published'],
|
|
327
|
+
Author.where { :id.in?(subquery.call) }.pluck(:name))
|
|
328
|
+
assert_equal(['drafting'],
|
|
329
|
+
Author.where { !:id.in?(subquery.call) }.pluck(:name))
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# == passes a Range or an Array through as a value rather than expanding it,
|
|
333
|
+
# so that it compares against a PostgreSQL range or array column. The SQL
|
|
334
|
+
# literal depends on the column type, so assert on the Arel node instead.
|
|
335
|
+
def test_equal_range_is_an_equality
|
|
336
|
+
node = ActiveRecord::Refined::AST::Comparison.new(:period, :==, 18..65).
|
|
337
|
+
to_arel(User.arel_table)
|
|
338
|
+
assert_instance_of(Arel::Nodes::Equality, node)
|
|
339
|
+
assert_equal(18..65, node.right.value)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def test_equal_array_is_an_equality
|
|
343
|
+
node = ActiveRecord::Refined::AST::Comparison.new(:tags, :==, [1, 2, 3]).
|
|
344
|
+
to_arel(User.arel_table)
|
|
345
|
+
assert_instance_of(Arel::Nodes::Equality, node)
|
|
346
|
+
assert_equal([1, 2, 3], node.right.value)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def test_not_equal_array_is_an_inequality
|
|
350
|
+
node = ActiveRecord::Refined::AST::Comparison.new(:tags, :!=, [1, 2, 3]).
|
|
351
|
+
to_arel(User.arel_table)
|
|
352
|
+
assert_instance_of(Arel::Nodes::NotEqual, node)
|
|
353
|
+
assert_equal([1, 2, 3], node.right.value)
|
|
93
354
|
end
|
|
94
355
|
|
|
95
356
|
def test_qualified_column
|
|
96
|
-
|
|
357
|
+
assert_sql(/WHERE "users"."name" = 'matz'/,
|
|
97
358
|
User.where { :users[:name] == 'matz' }.to_sql)
|
|
98
359
|
end
|
|
99
360
|
|
|
100
361
|
def test_column_to_column_comparison
|
|
101
362
|
sql = User.where { :users[:name] == :users[:age] }.to_sql
|
|
102
|
-
|
|
363
|
+
assert_sql(/"users"."name" = "users"."age"/, sql)
|
|
103
364
|
end
|
|
104
365
|
|
|
105
366
|
def test_joins_with_block
|
|
106
367
|
sql = Author.joins(:posts) { :posts[:author_id] == :authors[:id] }.to_sql
|
|
107
|
-
|
|
368
|
+
assert_sql(/INNER JOIN "posts" ON "posts"."author_id" = "authors"."id"/, sql)
|
|
108
369
|
end
|
|
109
370
|
|
|
110
371
|
def test_left_outer_joins_with_block
|
|
111
372
|
sql = Author.left_outer_joins(:posts) { :posts[:author_id] == :authors[:id] }.to_sql
|
|
112
|
-
|
|
373
|
+
assert_sql(/LEFT OUTER JOIN "posts" ON "posts"."author_id" = "authors"."id"/, sql)
|
|
113
374
|
end
|
|
114
375
|
|
|
115
376
|
def test_select_aggregate
|
|
116
|
-
|
|
377
|
+
assert_sql(/SELECT SUM\("users"."age"\)/,
|
|
117
378
|
User.select { :age.sum }.to_sql)
|
|
118
379
|
end
|
|
119
380
|
|
|
120
381
|
def test_select_aggregate_qualified
|
|
121
|
-
|
|
382
|
+
assert_sql(/SELECT COUNT\("users"."id"\)/,
|
|
122
383
|
User.select { :users[:id].count }.to_sql)
|
|
123
384
|
end
|
|
124
385
|
|
|
125
386
|
def test_select_average
|
|
126
|
-
|
|
387
|
+
assert_sql(/SELECT AVG\("users"."age"\)/,
|
|
127
388
|
User.select { :age.average }.to_sql)
|
|
128
389
|
end
|
|
129
390
|
|
|
130
391
|
def test_select_maximum
|
|
131
|
-
|
|
392
|
+
assert_sql(/SELECT MAX\("users"."age"\)/,
|
|
132
393
|
User.select { :age.maximum }.to_sql)
|
|
133
394
|
end
|
|
134
395
|
|
|
135
396
|
def test_select_minimum
|
|
136
|
-
|
|
397
|
+
assert_sql(/SELECT MIN\("users"."age"\)/,
|
|
137
398
|
User.select { :age.minimum }.to_sql)
|
|
138
399
|
end
|
|
139
400
|
|
|
140
401
|
def test_having_aggregate
|
|
141
402
|
sql = User.group(:name).having { :age.sum > 100 }.to_sql
|
|
142
|
-
|
|
143
|
-
|
|
403
|
+
assert_sql(/GROUP BY "users"."name"/, sql)
|
|
404
|
+
assert_sql(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
144
405
|
end
|
|
145
406
|
|
|
146
407
|
def test_select_avg_function
|
|
147
|
-
|
|
408
|
+
assert_sql(/SELECT AVG\("users"."age"\)/,
|
|
148
409
|
User.select { avg(:age) }.to_sql)
|
|
149
410
|
end
|
|
150
411
|
|
|
151
412
|
def test_select_count_function
|
|
152
|
-
|
|
413
|
+
assert_sql(/SELECT COUNT\("users"."id"\)/,
|
|
153
414
|
User.select { count(:id) }.to_sql)
|
|
154
415
|
end
|
|
155
416
|
|
|
417
|
+
def test_select_count_star
|
|
418
|
+
assert_sql(/SELECT COUNT\(\*\)/,
|
|
419
|
+
User.select { count(:*) }.to_sql)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def test_select_count_star_alias
|
|
423
|
+
assert_sql(/SELECT COUNT\(\*\) AS cnt/,
|
|
424
|
+
User.select { count(:*).as(:cnt) }.to_sql)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def test_having_count_star
|
|
428
|
+
sql = Author.joins(:posts) { :posts[:author_id] == :authors[:id] }.
|
|
429
|
+
group { :authors[:id] }.
|
|
430
|
+
having { count(:*) > 1 }.to_sql
|
|
431
|
+
assert_sql(/HAVING COUNT\(\*\) > 1/, sql)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def test_order_count_star
|
|
435
|
+
assert_sql(/ORDER BY COUNT\(\*\) DESC/,
|
|
436
|
+
User.group(:name).order { count(:*).desc }.to_sql)
|
|
437
|
+
end
|
|
438
|
+
|
|
156
439
|
def test_select_sum_function
|
|
157
|
-
|
|
440
|
+
assert_sql(/SELECT SUM\("users"."age"\)/,
|
|
158
441
|
User.select { sum(:age) }.to_sql)
|
|
159
442
|
end
|
|
160
443
|
|
|
161
444
|
def test_select_min_function
|
|
162
|
-
|
|
445
|
+
assert_sql(/SELECT MIN\("users"."age"\)/,
|
|
163
446
|
User.select { min(:age) }.to_sql)
|
|
164
447
|
end
|
|
165
448
|
|
|
166
449
|
def test_select_max_function
|
|
167
|
-
|
|
450
|
+
assert_sql(/SELECT MAX\("users"."age"\)/,
|
|
168
451
|
User.select { max(:age) }.to_sql)
|
|
169
452
|
end
|
|
170
453
|
|
|
171
454
|
def test_function_qualified_column
|
|
172
|
-
|
|
455
|
+
assert_sql(/SELECT AVG\("users"."age"\)/,
|
|
173
456
|
User.select { avg(:users[:age]) }.to_sql)
|
|
174
457
|
end
|
|
175
458
|
|
|
176
459
|
def test_having_function
|
|
177
460
|
sql = User.group(:name).having { sum(:age) > 100 }.to_sql
|
|
178
|
-
|
|
179
|
-
|
|
461
|
+
assert_sql(/GROUP BY "users"."name"/, sql)
|
|
462
|
+
assert_sql(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
180
463
|
end
|
|
181
464
|
|
|
182
465
|
def test_function_and_method_syntax_match
|
|
@@ -185,123 +468,138 @@ class TestBlockSyntax < Minitest::Test
|
|
|
185
468
|
end
|
|
186
469
|
|
|
187
470
|
def test_upper_function
|
|
188
|
-
|
|
471
|
+
assert_sql(/SELECT UPPER\("users"."name"\)/,
|
|
189
472
|
User.select { upper(:name) }.to_sql)
|
|
190
473
|
end
|
|
191
474
|
|
|
192
475
|
def test_lower_function
|
|
193
|
-
|
|
476
|
+
assert_sql(/SELECT LOWER\("users"."name"\)/,
|
|
194
477
|
User.select { lower(:name) }.to_sql)
|
|
195
478
|
end
|
|
196
479
|
|
|
197
480
|
def test_length_function_in_where
|
|
198
|
-
|
|
481
|
+
assert_sql(/WHERE LENGTH\("users"."name"\) > 3/,
|
|
199
482
|
User.where { length(:name) > 3 }.to_sql)
|
|
200
483
|
end
|
|
201
484
|
|
|
202
485
|
def test_coalesce_function_with_literal
|
|
203
|
-
|
|
486
|
+
assert_sql(/SELECT COALESCE\("users"."name", 'unknown'\)/,
|
|
204
487
|
User.select { coalesce(:name, 'unknown') }.to_sql)
|
|
205
488
|
end
|
|
206
489
|
|
|
207
490
|
def test_function_comparison
|
|
208
|
-
|
|
491
|
+
assert_sql(/WHERE UPPER\("users"."name"\) = 'MATZ'/,
|
|
209
492
|
User.where { upper(:name) == 'MATZ' }.to_sql)
|
|
210
493
|
end
|
|
211
494
|
|
|
495
|
+
def test_function_like
|
|
496
|
+
assert_sql(/WHERE UPPER\("users"."name"\) LIKE 'MA%'/,
|
|
497
|
+
User.where { upper(:name).like?('MA%') }.to_sql)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def test_function_in
|
|
501
|
+
assert_sql(/WHERE UPPER\("users"."name"\) IN \('MATZ', 'NOBU'\)/,
|
|
502
|
+
User.where { upper(:name).in?(%w[MATZ NOBU]) }.to_sql)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def test_aggregate_in
|
|
506
|
+
assert_sql(/HAVING SUM\("users"."age"\) BETWEEN 1 AND 10/,
|
|
507
|
+
User.group(:name).having { :age.sum.in?(1..10) }.to_sql)
|
|
508
|
+
end
|
|
509
|
+
|
|
212
510
|
def test_nested_function
|
|
213
|
-
|
|
511
|
+
assert_sql(/SELECT UPPER\(COALESCE\("users"."name", 'x'\)\)/,
|
|
214
512
|
User.select { upper(coalesce(:name, 'x')) }.to_sql)
|
|
215
513
|
end
|
|
216
514
|
|
|
217
515
|
def test_function_qualified_column_arg
|
|
218
|
-
|
|
516
|
+
assert_sql(/SELECT UPPER\("users"."name"\)/,
|
|
219
517
|
User.select { upper(:users[:name]) }.to_sql)
|
|
220
518
|
end
|
|
221
519
|
|
|
222
520
|
def test_select_multiple_fields
|
|
223
|
-
|
|
521
|
+
assert_sql(/SELECT UPPER\("users"."name"\), "users"."age"/,
|
|
224
522
|
User.select { [upper(:name), :age] }.to_sql)
|
|
225
523
|
end
|
|
226
524
|
|
|
227
525
|
def test_select_multiple_columns
|
|
228
|
-
|
|
526
|
+
assert_sql(/SELECT "users"."name", "users"."age"/,
|
|
229
527
|
User.select { [:name, :age] }.to_sql)
|
|
230
528
|
end
|
|
231
529
|
|
|
232
530
|
def test_select_multiple_with_aggregate
|
|
233
|
-
|
|
531
|
+
assert_sql(/SELECT "users"."name", SUM\("users"."age"\)/,
|
|
234
532
|
User.select { [:name, sum(:age)] }.to_sql)
|
|
235
533
|
end
|
|
236
534
|
|
|
237
535
|
def test_select_function_with_alias
|
|
238
|
-
|
|
536
|
+
assert_sql(/SELECT UPPER\("users"."name"\) AS upper_name, "users"."age"/,
|
|
239
537
|
User.select { [upper(:name).as(:upper_name), :age] }.to_sql)
|
|
240
538
|
end
|
|
241
539
|
|
|
242
540
|
def test_select_column_alias
|
|
243
|
-
|
|
541
|
+
assert_sql(/SELECT "users"."name" AS n/,
|
|
244
542
|
User.select { :name.as(:n) }.to_sql)
|
|
245
543
|
end
|
|
246
544
|
|
|
247
545
|
def test_select_qualified_column_alias
|
|
248
|
-
|
|
546
|
+
assert_sql(/SELECT "users"."name" AS n/,
|
|
249
547
|
User.select { :users[:name].as(:n) }.to_sql)
|
|
250
548
|
end
|
|
251
549
|
|
|
252
550
|
def test_select_aggregate_alias
|
|
253
|
-
|
|
551
|
+
assert_sql(/SELECT COUNT\("users"."id"\) AS cnt/,
|
|
254
552
|
User.select { count(:id).as(:cnt) }.to_sql)
|
|
255
553
|
end
|
|
256
554
|
|
|
257
555
|
def test_order_default_asc
|
|
258
|
-
|
|
556
|
+
assert_sql(/ORDER BY "users"."age"/,
|
|
259
557
|
User.order { :age }.to_sql)
|
|
260
558
|
end
|
|
261
559
|
|
|
262
560
|
def test_order_desc
|
|
263
|
-
|
|
561
|
+
assert_sql(/ORDER BY "users"."age" DESC/,
|
|
264
562
|
User.order { :age.desc }.to_sql)
|
|
265
563
|
end
|
|
266
564
|
|
|
267
565
|
def test_order_asc
|
|
268
|
-
|
|
566
|
+
assert_sql(/ORDER BY "users"."age" ASC/,
|
|
269
567
|
User.order { :age.asc }.to_sql)
|
|
270
568
|
end
|
|
271
569
|
|
|
272
570
|
def test_order_multiple
|
|
273
|
-
|
|
571
|
+
assert_sql(/ORDER BY "users"."age" DESC, "users"."name" ASC/,
|
|
274
572
|
User.order { [:age.desc, :name.asc] }.to_sql)
|
|
275
573
|
end
|
|
276
574
|
|
|
277
575
|
def test_order_qualified_column
|
|
278
|
-
|
|
576
|
+
assert_sql(/ORDER BY "users"."name" DESC/,
|
|
279
577
|
User.order { :users[:name].desc }.to_sql)
|
|
280
578
|
end
|
|
281
579
|
|
|
282
580
|
def test_group_single
|
|
283
|
-
|
|
581
|
+
assert_sql(/GROUP BY "users"."name"/,
|
|
284
582
|
User.group { :name }.to_sql)
|
|
285
583
|
end
|
|
286
584
|
|
|
287
585
|
def test_group_multiple
|
|
288
|
-
|
|
586
|
+
assert_sql(/GROUP BY "users"."name", "users"."age"/,
|
|
289
587
|
User.group { [:name, :age] }.to_sql)
|
|
290
588
|
end
|
|
291
589
|
|
|
292
590
|
def test_group_qualified_column
|
|
293
|
-
|
|
591
|
+
assert_sql(/GROUP BY "users"."name"/,
|
|
294
592
|
User.group { :users[:name] }.to_sql)
|
|
295
593
|
end
|
|
296
594
|
|
|
297
595
|
def test_group_with_having
|
|
298
596
|
sql = User.group { :name }.having { sum(:age) > 100 }.to_sql
|
|
299
|
-
|
|
300
|
-
|
|
597
|
+
assert_sql(/GROUP BY "users"."name"/, sql)
|
|
598
|
+
assert_sql(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
301
599
|
end
|
|
302
600
|
|
|
303
601
|
def test_default_where_syntax
|
|
304
|
-
|
|
602
|
+
assert_sql(/WHERE "users"."name" = 'Ruby' AND "users"."age" = 19/,
|
|
305
603
|
User.where(name: 'Ruby', age: 19).to_sql)
|
|
306
604
|
end
|
|
307
605
|
end
|