提交 7d3bff1f 编写于 作者: R Ryuta Kamizono

More concise Arel `Or` ast and make `Or` visitor non recursive

Before this, 1000 `Or` nodes will raise "stack level too deep" due to
visiting too deep Arel ast.

This makes more concise Arel `Or` ast and `Or` visitor non recursive if
`Or` nodes are adjoined, as a result, "stack level too deep" is no
longer raised.

```ruby
class Post < ActiveRecord::Base
end

posts = (0..500).map { |i| Post.where(id: i) }

Benchmark.ips do |x|
  x.report("inject scopes") { posts.inject(&:or).to_sql }
end
```

Before:

```
Warming up --------------------------------------
      where with ids     9.000  i/100ms
Calculating -------------------------------------
      where with ids     96.126  (± 2.1%) i/s -    486.000  in   5.058960s
```

After:

```
Warming up --------------------------------------
       inject scopes    10.000  i/100ms
Calculating -------------------------------------
       inject scopes    101.714  (± 2.9%) i/s -    510.000  in   5.018880s
```

Fixes #39032.
上级 a5469f02
......@@ -39,10 +39,16 @@ def or(other)
if left.empty? || right.empty?
common
else
or_clause = WhereClause.new(
[left.ast.or(right.ast)],
)
common + or_clause
left = left.ast
left = left.expr if left.is_a?(Arel::Nodes::Grouping)
right = right.ast
right = right.expr if right.is_a?(Arel::Nodes::Grouping)
or_clause = Arel::Nodes::Or.new(left, right)
common.predicates << Arel::Nodes::Grouping.new(or_clause)
common
end
end
......
......@@ -578,9 +578,18 @@ def visit_Arel_Nodes_And(o, collector)
end
def visit_Arel_Nodes_Or(o, collector)
collector = visit o.left, collector
collector << " OR "
visit o.right, collector
stack = [o.right, o.left]
while o = stack.pop
if o.is_a?(Arel::Nodes::Or)
stack.push o.right, o.left
else
visit o, collector
collector << " OR " unless stack.empty?
end
end
collector
end
def visit_Arel_Nodes_Assignment(o, collector)
......
......@@ -4,11 +4,11 @@
require "models/author"
require "models/categorization"
require "models/post"
require "models/citation"
module ActiveRecord
class OrTest < ActiveRecord::TestCase
fixtures :posts
fixtures :authors, :author_addresses
fixtures :posts, :authors, :author_addresses
def test_or_with_relation
expected = Post.where("id = 1 or id = 2").to_a
......@@ -151,4 +151,20 @@ def test_structurally_incompatible_values
end
end
end
# The maximum expression tree depth is 1000 by default for SQLite3.
# https://www.sqlite.org/limits.html#max_expr_depth
unless current_adapter?(:SQLite3Adapter)
class TooManyOrTest < ActiveRecord::TestCase
fixtures :citations
def test_too_many_or
citations = 6000.times.map do |i|
Citation.where(id: i, book2_id: i * i)
end
assert_equal 6000, citations.inject(&:or).count
end
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册