suggest-indexes proposes composite indexes that include the primary key or a unique field
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
Research direction
Start in cli/orm_lens/query_analyzer.py at suggest_indexes(), where pk_name and target_model are available, then read the composite loop at the top of _propose_indexes(). Build a set of unique single columns (the PK, pk, primary_key=True and unique=True fields, and OneToOneFields) and skip any composite that contains one of them, leaving the single-field logic alone. Done when the Story repro yields only ['status'] and ['title'], pk= behaves like id=, and cli/tests/test_query_analyzer.py plus pytest, ruff and mypy pass.
Written by the indexing model from the issue text.
Description
Source: weblate — trans.Unit: models.Index(fields=['id', 'translation'])
What happens
filter(id=pk, title=title) seen twice gives models.Index(fields=['id', 'title']). The primary key is unique: an equality lookup on it returns at most one row, so the PK index already answers the query and a composite led by id only costs writes and disk. The same is true for any unique=True field.
_propose_indexes() uses already_indexed for single-field proposals only. The composite loop checks the M2M/reverse filter from #252/#263 and existing Meta groups, nothing else. pk= is folded into id before that, so both spellings hit it.
On weblate trans.Unit gets models.Index(fields=['id', 'translation']).
Repro
board/models.py
from django.db import models
class Story(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
status = models.CharField(max_length=20)
board/stats.py
from .models import Story
def by_id_and_title(pk, title):
return Story.objects.filter(id=pk, title=title).first()
def by_id_and_title_again(pk, title):
return Story.objects.get(id=pk, title=title)
def by_pk_and_status(pk, status):
return Story.objects.filter(pk=pk, status=status).exists()
def by_pk_and_status_again(pk, status):
return Story.objects.filter(pk=pk, status=status).first()
def by_slug_and_status(slug, status):
return Story.objects.filter(slug=slug, status=status).first()
def by_slug_and_status_again(slug, status):
return Story.objects.filter(slug=slug, status=status).exists()
$ orm-lens suggest-indexes board.Story
target: board.Story
proposed Meta.indexes (5):
models.Index(fields=['id', 'status']), # 2 composite filter sites
models.Index(fields=['id', 'title']), # 2 composite filter sites
models.Index(fields=['slug', 'status']), # 2 composite filter sites
models.Index(fields=['status']), # 4 filter sites
models.Index(fields=['title']), # 2 filter sites
Expected: only ['status'] and ['title']. The usage table can keep listing id + title etc.
Where
cli/orm_lens/query_analyzer.py
- line 497,
_propose_indexes(); the composite loop is right at the top of the function body; - line 309,
_indexed_single_fields(): buildsalready_indexed, but it also containsdb_index=Truefields and FKs, which are not unique, so it can't be reused as is; - line 598,
suggest_indexes():pk_name(line 675) andtarget_modelare both there.
Suggested fix
In suggest_indexes(), build the set of unique single columns: pk_name, "pk", every field with primary_key=True or unique=True (the _PK_TRUE_RE / _UNIQUE_TRUE_RE regexes are already in the module), and OneToOneFields (unique by definition). Pass it to _propose_indexes() as a new keyword argument and skip a composite combo that contains any of these names.
Don't touch the single-field and order_by logic.
How to test
cli/tests/test_query_analyzer.py: the repro gives exactly['status']and['title'].filter(pk=..., ...)is dropped the same asfilter(id=..., ...).- A model with
code = models.CharField(primary_key=True)drops a['code', 'title']composite. - A composite of two non-unique fields (
filter(title=..., status=...)) is still proposed. cd cli && python -m pytest -q,ruffandmypypass.
Difficulty: good first issue, one set and one if.
- Dominant language
- Python
- Stars
- 84
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from FROWNINGdev/django-orm-lens
-
`migration-risk` text output prints `app.None` for graph-level findingsPossibly taken @phoenixbycaselka claimed this today. Openbug good first issue python
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
FROWNINGdev/django-orm-lens#268 ·
-
Finish the Chinese rule reference — DOL001–DOL007May be free again @harbinresearcher claimed this 64 days ago, and no pull request is open. Opendocumentation good first issue help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
FROWNINGdev/django-orm-lens#81 · 2 comments ·
-
Fields inherited from Django's `AbstractBaseUser` / `PermissionsMixin` / `AbstractUser` are missingOpenbug good first issue python
Difficulty 3/5 1-2 days Newbie friendliness 62/100
FROWNINGdev/django-orm-lens#267 ·
-
bug good first issue python
Difficulty 3/5 Half a day Newbie friendliness 78/100
FROWNINGdev/django-orm-lens#266 ·
-
enhancement help wanted python
Difficulty 4/5 3-5 days Newbie friendliness 35/100
FROWNINGdev/django-orm-lens#254 ·
All issues in FROWNINGdev/django-orm-lens
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
roman-corgi/corgisim#327 ·
Maintainers usually reply within 2 days
-
[Bug]: opencode_go chat route drops reasoning_effort (even 'none'), while the same endpoint honors it via custom_llm_provider=openaiPossibly taken @arvindkumar-ship-it claimed this today. Openllm translation
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
BerriAI/litellm#51871 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
PolicyEngine backdates South Carolina’s EITC into 2015–2017Possibly taken @MaxGhenis claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
PolicyEngine/policyengine-us#10944 ·
Maintainers usually reply within 2 days
-
fix(frontend): resolve SSE parser crash on unhandled Ref methods and undefined actions identifierOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
kodokbakar/MiroFish-Local#94 ·
Maintainers usually reply within 1 day