Skip to content

Commit af2a4c3

Browse files
committed
ruby: Adjust qhelp and query to avoid async calls (also rename)
1 parent 380c12f commit af2a4c3

File tree

4 files changed

+30
-35
lines changed

4 files changed

+30
-35
lines changed

ruby/ql/src/queries/performance/CouldBeAsync.qhelp renamed to ruby/ql/src/queries/performance/CouldBeHoisted.qhelp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ In those cases, where the query includes a <code>pluck</code> call, the query co
1111
</p>
1212
</overview>
1313
<recommendation>
14-
<p>If possible, split the loop into two. The first creates a map of promises resolving to the async query results. The second runs throuhg this map, waiting on each promise, and does whatever the original loop did with the result of the query.</p>
14+
<p>If possible, pull the query out of the loop, thus replacing the many calls with a single one.
15+
</p>
1516
</recommendation>
1617
<example>
17-
<p>The following (suboptimal) example code executes a series of ActiveRecord queries in a loop. The queries are independent of each other, so they could be executed in parallel to improve performance.</p>
18+
<p>The following (suboptimal) example code queries the User object in each iteration of the loop:</p>
1819
<sample src="examples/straight_loop.rb" />
19-
<p>To be able to fetch the necessary information asynchronously, we first pull it out into its own (implicit) loop:
20+
<p>To improve the performance, we instead query the User object once outside the loop, gathereing all necessary information:
2021
<sample src="examples/preload.rb" />
21-
<p>We can now use the <code>async_pluck</code> method to execute the queries in parallel.</p>
22-
<sample src="examples/async_pluck.rb" />
2322
</example>
2423
</qhelp>

ruby/ql/src/queries/performance/CouldBeAsync.ql renamed to ruby/ql/src/queries/performance/CouldBeHoisted.ql

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
2-
* @name Could be async
3-
* @description Use `ActiveRecord::Relation#load_async` to load records asynchronously.
2+
* @name Could be hoisted
3+
* @description Hoist Rails `ActiveRecord::Relation` query calls out of loops.
44
* @kind problem
55
* @problem.severity info
66
* @precision high
7-
* @id rb/could-be-async
7+
* @id rb/could-be-hoisted
88
* @tags performance
99
*/
1010

@@ -82,18 +82,14 @@ DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) {
8282
control = cond.getBranch(_).getAChild()
8383
}
8484

85-
from LoopingCall loop, DataFlow::CallNode call, string message
85+
from LoopingCall loop, DataFlow::CallNode call
8686
where
87+
// TODO: Filter loops over constants
8788
not call.getLocation().getFile().getAbsolutePath().matches("%test%") and
88-
not call = any(PluckCall p).chaines() and
89+
// not call = any(PluckCall p).chaines() and
8990
not usedInLoopControlGuard(call, _) and
9091
happensInInnermostLoop(loop, call) and
91-
(
92-
call instanceof ActiveRecordModelFinderCall and
93-
not call.getMethodName() in ["new", "create"] and
94-
message = "could be chained with load_async"
95-
or
96-
call instanceof PluckCall and
97-
message = "could be async_pluck"
98-
)
99-
select call, "This call happens inside $@, and " + message, loop, "this loop"
92+
call instanceof ActiveRecordModelFinderCall and
93+
not call.getMethodName() in ["new", "create"] //and
94+
// call.getLocation().getFile().getAbsolutePath().matches("%app/models/stafftools/%")
95+
select call, "This call happens inside $@, and could be hoisted.", loop, "this loop"
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Preload User data
22
user_data = User.where(login: repo_names_by_owner.keys).pluck(:login, :id, :type).to_h do |login, id, type|
3-
[login, { id: id, type: type == "User" ? "USER" : "ORGANIZATION" }]
4-
end
5-
6-
repo_names_by_owner.each do |owner_slug, repo_names|
7-
owner_info = user_data[owner_slug]
8-
owner_id = owner_info[:id]
9-
owner_type = owner_info[:type]
10-
rel_conditions = { owner_id: owner_id, name: repo_names }
11-
12-
nwo_rel = nwo_rel.or(RepositorySecurityCenterConfig.where(rel_conditions)) unless neg
13-
nwo_rel = nwo_rel.and(RepositorySecurityCenterConfig.where.not(rel_conditions)) if neg
14-
end
3+
[login, { id: id, type: type == "User" ? "USER" : "ORGANIZATION" }]
4+
end
5+
6+
repo_names_by_owner.each do |owner_slug, repo_names|
7+
owner_info = user_data[owner_slug]
8+
owner_id = owner_info[:id]
9+
owner_type = owner_info[:type]
10+
rel_conditions = { owner_id: owner_id, name: repo_names }
11+
12+
nwo_rel = nwo_rel.or(RepositorySecurityCenterConfig.where(rel_conditions)) unless neg
13+
nwo_rel = nwo_rel.and(RepositorySecurityCenterConfig.where.not(rel_conditions)) if neg
14+
end

ruby/ql/src/queries/performance/rb-could-be-async.md renamed to ruby/ql/src/queries/performance/rb-could-be-hoisted.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
## Description
44

5-
When an AcriveRecord query is executed in a loop, it potentially an n+1 problem.
5+
When a Rails ActiveRecord query is executed in a loop, it is potentially an n+1 problem.
66
This query identifies situations where an ActiveRecord query execution could be pulled out of a loop.
77

88
## Recommendation
9-
Pull the query out of the loop, thus replacing the many calls with a single one.
9+
If possible, pull the query out of the loop, thus replacing the many calls with a single one.
1010

1111
## Examples
1212

@@ -31,8 +31,8 @@ To improve the performance, we instead query the User object once outside the lo
3131
```ruby
3232
# Preload User data
3333
user_data = User.where(login: repo_names_by_owner.keys).pluck(:login, :id, :type).to_h do |login, id, type|
34-
[login, { id: id, type: type == "User" ? "USER" : "ORGANIZATION" }]
35-
end
34+
[login, { id: id, type: type == "User" ? "USER" : "ORGANIZATION" }]
35+
end
3636

3737
repo_names_by_owner.each do |owner_slug, repo_names|
3838
owner_info = user_data[owner_slug]

0 commit comments

Comments
 (0)