Packages
metastatic
0.7.4
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/analysis/business_logic/missing_preload.ex
defmodule Metastatic.Analysis.BusinessLogic.MissingPreload do
@moduledoc """
Detects database queries without eager loading (N+1 query potential).
Universal pattern: accessing related data without preloading/prefetching.
## Examples
**Python (Django ORM without select_related):**
```python
users = User.objects.all() # Should use select_related('profile')
for user in users:
print(user.profile.bio) # N+1 query - profile accessed in loop
```
**JavaScript (Sequelize without include):**
```javascript
const users = await User.findAll(); # Should include: [{ model: Profile }]
users.forEach(user => {
console.log(user.profile.bio); # N+1 - profile fetched per user
});
```
**Elixir (Ecto without preload):**
```elixir
users = Repo.all(User) # Should use preload(:profile)
Enum.each(users, fn user ->
IO.puts(user.profile.bio) # N+1 - profile loaded per user
end)
```
**C# (Entity Framework without Include):**
```csharp
var users = context.Users.ToList(); # Should use Include(u => u.Profile)
foreach (var user in users) {
Console.WriteLine(user.Profile.Bio); # Lazy loading - N+1 queries
}
```
**Go (GORM without Preload):**
```go
var users []User
db.Find(&users) # Should use Preload(\"Profile\")
for _, user := range users {
fmt.Println(user.Profile.Bio) # N+1 - separate query per user
}
```
**Java (Hibernate without JOIN FETCH):**
```java
List<User> users = session.createQuery(\"FROM User\").list(); # Should use JOIN FETCH
for (User user : users) {
System.out.println(user.getProfile().getBio()); # Lazy loading triggers N queries
}
```
**Ruby (ActiveRecord without includes):**
```ruby
users = User.all # Should use User.includes(:profile)
users.each do |user|
puts user.profile.bio # N+1 - profile query per user
end
```
"""
@behaviour Metastatic.Analysis.Analyzer
alias Metastatic.Analysis.Analyzer
@query_functions ~w[
all find get query select fetch load
findall getall findMany where filter
]
@impl true
def info do
%{
name: :missing_preload,
category: :performance,
description: "Detects potential N+1 queries from missing eager loading",
severity: :warning,
explanation: "Use eager loading (preload/include) to avoid N+1 query problems",
configurable: true
}
end
@impl true
# Handle location-aware nodes
def analyze({:collection_op, :map, _fn, collection, _loc} = node, _context) do
# Check if collection comes from DB query
if from_database_query?(collection) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :performance,
severity: :warning,
message: "Mapping over database results without eager loading - potential N+1 queries",
node: node,
metadata: %{
suggestion: "Use preload/include/select_related to eager load associations"
}
)
]
else
[]
end
end
def analyze({:collection_op, :map, _fn, collection} = node, _context) do
# Check if collection comes from DB query
if from_database_query?(collection) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :performance,
severity: :warning,
message: "Mapping over database results without eager loading - potential N+1 queries",
node: node,
metadata: %{
suggestion: "Use preload/include/select_related to eager load associations"
}
)
]
else
[]
end
end
def analyze({:loop, :for, _iterator, collection, _body} = node, _context) do
# Check if iterating over DB results
if from_database_query?(collection) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :performance,
severity: :warning,
message: "Looping over database results - ensure associations are preloaded",
node: node,
metadata: %{
suggestion: "Add eager loading to avoid N+1 queries"
}
)
]
else
[]
end
end
def analyze(_node, _context), do: []
# Handle location-aware nodes
defp from_database_query?({:function_call, fn_name, _args, _loc}) when is_binary(fn_name) do
fn_lower = String.downcase(fn_name)
String.contains?(fn_lower, @query_functions)
end
defp from_database_query?({:function_call, fn_name, _args}) when is_binary(fn_name) do
fn_lower = String.downcase(fn_name)
String.contains?(fn_lower, @query_functions)
end
defp from_database_query?({:variable, name, _loc}) when is_binary(name) do
name_lower = String.downcase(name)
String.contains?(name_lower, [
"user",
"post",
"item",
"record",
"result",
"data"
])
end
defp from_database_query?({:variable, name}) when is_binary(name) do
name_lower = String.downcase(name)
String.contains?(name_lower, [
"user",
"post",
"item",
"record",
"result",
"data"
])
end
defp from_database_query?(_), do: false
end