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

Abierto Apto para principiantes
#269 0 comentarios 0 reacciones 0 asignados Ver en GitHub

@bfs2021 ya está trabajando en esto.

Desde el 14/11/2026.

  • #272 de @bfs2021 — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
82/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
django, python
Área
cli, database

Línea de trabajo

Empieza en cli/orm_lens/query_analyzer.py en suggest_indexes(), donde están disponibles pk_name y target_model, y luego lee el bucle de compuestos al inicio de _propose_indexes(). Construye un conjunto de columnas únicas individuales (la PK, pk, los campos con primary_key=True o unique=True, y los OneToOneFields) y omite cualquier compuesto que contenga una de ellas, dejando intacta la lógica de campos individuales. Está listo cuando el reproductor de Story solo produce ['status'] y ['title'], pk= se comporta como id=, y cli/tests/test_query_analyzer.py junto con pytest, ruff y mypy pasan.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Python
Estrellas
84
Forks
13
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de FROWNINGdev/django-orm-lens

Todos los issues de FROWNINGdev/django-orm-lens

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.