Skip to content

Commit 730367e

Browse files
authored
Merge pull request rails#54951 from skipkayhil/hm-where-sql-literal
Enable passing retryable `SqlLiteral`s to `#where`
2 parents a017672 + f3763e6 commit 730367e

File tree

7 files changed

+29
-28
lines changed

7 files changed

+29
-28
lines changed

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Enable passing retryable SqlLiterals to `#where`.
2+
3+
*Hartley McGuire*
4+
15
* Set default for primary keys in `insert_all`/`upsert_all`.
26

37
Previously in Postgres, updating and inserting new records in one upsert wasn't possible

activerecord/lib/active_record/relation/predicate_builder.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def with(table)
8282
attr_writer :table
8383

8484
def expand_from_hash(attributes, &block)
85-
return ["1=0"] if attributes.empty?
85+
return [Arel.sql("1=0", retryable: true)] if attributes.empty?
8686

8787
attributes.flat_map do |key, value|
8888
if key.is_a?(Array) && key.size == 1

activerecord/lib/active_record/relation/query_methods.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1630,7 +1630,7 @@ def build_where_clause(opts, rest = []) # :nodoc:
16301630
elsif opts.include?("?")
16311631
parts = [build_bound_sql_literal(opts, rest)]
16321632
else
1633-
parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1633+
parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
16341634
end
16351635
when Hash
16361636
opts = opts.transform_keys do |key|

activerecord/lib/active_record/relation/where_clause.rb

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def predicates_with_wrapped_sql_literals
188188
non_empty_predicates.map do |node|
189189
case node
190190
when Arel::Nodes::SqlLiteral, ::String
191-
wrap_sql_literal(node)
191+
Arel::Nodes::Grouping.new(node)
192192
else node
193193
end
194194
end
@@ -199,13 +199,6 @@ def non_empty_predicates
199199
predicates - ARRAY_WITH_EMPTY_STRING
200200
end
201201

202-
def wrap_sql_literal(node)
203-
if ::String === node
204-
node = Arel.sql(node)
205-
end
206-
Arel::Nodes::Grouping.new(node)
207-
end
208-
209202
def extract_node_value(node)
210203
if node.respond_to?(:value_before_type_cast)
211204
node.value_before_type_cast

activerecord/lib/arel.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ module Arel
5050
# Use this option only if the SQL is idempotent, as it could be executed
5151
# more than once.
5252
def self.sql(sql_string, *positional_binds, retryable: false, **named_binds)
53-
if positional_binds.empty? && named_binds.empty?
53+
if Arel::Nodes::SqlLiteral === sql_string
54+
sql_string
55+
elsif positional_binds.empty? && named_binds.empty?
5456
Arel::Nodes::SqlLiteral.new(sql_string, retryable: retryable)
5557
else
5658
Arel::Nodes::BoundSqlLiteral.new sql_string, positional_binds, named_binds

activerecord/test/cases/adapter_test.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ def teardown
706706
notifications = capture_notifications("sql.active_record") do
707707
assert (a = Author.first)
708708
assert Post.where(id: [1, 2]).first
709+
assert Post.where(Arel.sql("id IN (1,2)", retryable: true)).first
709710
assert Post.find(1)
710711
assert Post.find_by(title: "Welcome to the weblog")
711712
assert_predicate Post, :exists?
@@ -714,7 +715,7 @@ def teardown
714715
Author.group(:name).count
715716
end.select { |n| n.payload[:name] != "SCHEMA" }
716717

717-
assert_equal 8, notifications.length
718+
assert_equal 9, notifications.length
718719

719720
notifications.each do |n|
720721
assert n.payload[:allow_retry], "#{n.payload[:sql]} was not retryable"
@@ -727,6 +728,7 @@ def teardown
727728
notifications = capture_notifications("sql.active_record") do
728729
assert_not_nil (a = Author.first)
729730
assert_not_nil Post.where(id: [1, 2]).first
731+
assert Post.where(Arel.sql("id IN (1,2)", retryable: true)).first
730732
assert_not_nil Post.find(1)
731733
assert_not_nil Post.find_by(title: "Welcome to the weblog")
732734
assert_predicate Post, :exists?
@@ -735,7 +737,7 @@ def teardown
735737
Author.group(:name).count
736738
end.select { |n| n.payload[:name] != "SCHEMA" }
737739

738-
assert_equal 8, notifications.length
740+
assert_equal 9, notifications.length
739741

740742
notifications.each do |n|
741743
assert n.payload[:allow_retry], "#{n.payload[:sql]} was not retryable"

activerecord/test/cases/relation/where_clause_test.rb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ class WhereClauseTest < ActiveRecord::TestCase
1515
end
1616

1717
test "+ is associative, but not commutative" do
18-
a = WhereClause.new(["a"])
19-
b = WhereClause.new(["b"])
20-
c = WhereClause.new(["c"])
18+
a = WhereClause.new([Arel.sql("a")])
19+
b = WhereClause.new([Arel.sql("b")])
20+
c = WhereClause.new([Arel.sql("c")])
2121

2222
assert_equal a + (b + c), (a + b) + c
2323
assert_not_equal a + b, b + a
@@ -76,7 +76,7 @@ class WhereClauseTest < ActiveRecord::TestCase
7676

7777
test "a clause knows if it is empty" do
7878
assert_empty WhereClause.empty
79-
assert_not_empty WhereClause.new(["anything"])
79+
assert_not_empty WhereClause.new([Arel.sql("anything")])
8080
end
8181

8282
test "invert cannot handle nil" do
@@ -99,7 +99,7 @@ class WhereClauseTest < ActiveRecord::TestCase
9999
table["id"].lteq(2),
100100
table["id"].is_not_distinct_from(1),
101101
table["id"].is_distinct_from(2),
102-
"sql literal"
102+
Arel.sql("sql literal"),
103103
])
104104
expected = WhereClause.new([
105105
Arel::Nodes::Not.new(
@@ -114,7 +114,7 @@ class WhereClauseTest < ActiveRecord::TestCase
114114
table["id"].lteq(2),
115115
table["id"].is_not_distinct_from(1),
116116
table["id"].is_distinct_from(2),
117-
Arel::Nodes::Grouping.new("sql literal")
117+
Arel::Nodes::Grouping.new(Arel.sql("sql literal")),
118118
])
119119
)
120120
])
@@ -161,7 +161,7 @@ class WhereClauseTest < ActiveRecord::TestCase
161161
random_object = Object.new
162162
where_clause = WhereClause.new([
163163
table["id"].in([1, 2, 3]),
164-
"foo = bar",
164+
Arel.sql("foo = bar"),
165165
random_object,
166166
])
167167
expected = Arel::Nodes::And.new([
@@ -175,7 +175,7 @@ class WhereClauseTest < ActiveRecord::TestCase
175175

176176
test "ast removes any empty strings" do
177177
where_clause = WhereClause.new([table["id"].in([1, 2, 3])])
178-
where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""])
178+
where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), Arel.sql("")])
179179

180180
assert_equal where_clause.ast, where_clause_with_empty.ast
181181
end
@@ -232,12 +232,12 @@ class WhereClauseTest < ActiveRecord::TestCase
232232
test "or will use only common conditions if one side only has common conditions" do
233233
only_common = WhereClause.new([
234234
table["id"].eq(bind_param(1)),
235-
"foo = bar",
235+
Arel.sql("foo = bar"),
236236
])
237237

238238
common_with_extra = WhereClause.new([
239239
table["id"].eq(bind_param(1)),
240-
"foo = bar",
240+
Arel.sql("foo = bar"),
241241
table["extra"].eq(bind_param("pluto")),
242242
])
243243

@@ -247,13 +247,13 @@ class WhereClauseTest < ActiveRecord::TestCase
247247

248248
test "supports hash equality" do
249249
h = Hash.new(0)
250-
h[WhereClause.new(["a"])] += 1
251-
h[WhereClause.new(["a"])] += 1
252-
h[WhereClause.new(["b"])] += 1
250+
h[WhereClause.new([Arel.sql("a")])] += 1
251+
h[WhereClause.new([Arel.sql("a")])] += 1
252+
h[WhereClause.new([Arel.sql("b")])] += 1
253253

254254
expected = {
255-
WhereClause.new(["a"]) => 2,
256-
WhereClause.new(["b"]) => 1
255+
WhereClause.new([Arel.sql("a")]) => 2,
256+
WhereClause.new([Arel.sql("b")]) => 1
257257
}
258258
assert_equal expected, h
259259
end

0 commit comments

Comments
 (0)