suggest-indexes proposes composite indexes that include the primary key or a unique field

未关闭 适合新手
#269 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

@bfs2021 已经在做这个了。

开始于 2026年11月14日。

  • #272 来自 @bfs2021 —— 未关闭

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
82/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
django, python
领域
cli, database

调研方向

从 cli/orm_lens/query_analyzer.py 中的 suggest_indexes() 开始,那里可以用到 pk_name 和 target_model,然后阅读 _propose_indexes() 顶部的复合索引循环。构建一个唯一单列的集合(主键 PK、pk、带 primary_key=True 或 unique=True 的字段,以及 OneToOneFields),跳过包含其中任一列的复合索引,单字段逻辑保持不变。完成标准是:Story 复现只输出 ['status'] 和 ['title'],pk= 的行为与 id= 一致,并且 cli/tests/test_query_analyzer.py 以及 pytest、ruff、mypy 全部通过。

由索引模型根据 Issue 内容生成。

描述

bug good first issue python

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(): builds already_indexed, but it also contains db_index=True fields and FKs, which are not unique, so it can't be reused as is;
  • line 598, suggest_indexes(): pk_name (line 675) and target_model are 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 as filter(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, ruff and mypy pass.

Difficulty: good first issue, one set and one if.

主要语言
Python
星标
84
派生
13
PR 合并指标
30 天内没有已合并 PR

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

FROWNINGdev/django-orm-lens 的其他 Issue

查看 FROWNINGdev/django-orm-lens 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。