Changelog¶
Auto-included
This page pulls the changelog content directly from the CHANGELOG.md
file at the repository root. Changes are maintained in a single location
and rendered here automatically by MkDocs.
Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Upgrading to 2.1.0¶
nplusone, duplicate, and missing_index now respect their
ANALYZERS.<name>.enabled config setting, and every dispatch path
(middleware, pytest plugin, Celery integration, context manager,
check_queries/diagnose_project) now runs the full set of discovered
analyzers instead of a hardcoded subset. If you use check_queries
--baseline, regenerate your baseline after upgrading — the widened
analyzer coverage means an old baseline will report newly-covered findings as
regressions until it's refreshed. Comparing against a baseline saved with a
different query-doctor version now prints a non-blocking warning rather than
failing the check.
[2.1.0] - 2026-07-14¶
Added¶
IssueType.SERIALIZER_METHOD_FIELD— findings fromSerializerMethodAnalyzer(thecheck_serializersstatic analyzer) now carry their own issue type instead of sharingIssueType.DRF_SERIALIZERwith the deleted runtime analyzer.DRF_SERIALIZERremains in the enum for plugin/fixer compatibility.
Fixed¶
nplusone,duplicate, andmissing_indexanalyzers now respect theirANALYZERS.<name>.enabledconfig setting. Previously, disabling these three analyzers had no effect outsidefix_queries— they still ran and reported issues through the middleware, pytest plugin, Celery integration, context manager, andcheck_queries/diagnose_projectcommands.- Middleware, context manager,
check_queries, Celery integration, and the pytest plugin now dispatch throughdiscover_analyzers()instead of five separate hardcoded, inconsistent analyzer lists (3-5 of the built-ins each). Every analyzer's ownis_enabled()gate (above) is what keeps config toggles honored now that dispatch is no longer hand-filtered per site. serializer_methodnow has aDEFAULT_CONFIGentry, soANALYZERS.serializer_method.enabled = Falseactually disables it. Previously there was no config key to set, so the analyzer always ran.fat_select's column-count threshold is now configurable viaANALYZERS.fat_select.threshold(previously only overridable by constructing the analyzer directly; the config key didn't exist).fix_queries --issue-typenow validates against the five fixer-backed issue types instead of silently accepting any string and producing zero fixes on a typo.check_queries --baselinenow tracks the query-doctor version the baseline was saved with (previously hardcoded to a stale"2.0.0"literal) and prints a non-blocking warning — not a failure — when comparing against a baseline saved with a different version.
Removed¶
- Removed
DRFSerializerAnalyzer, a builtin analyzer that always returned no results through any code path reachable fromfix_queries, the middleware, or any management command. DRF serializer N+1 detection is unaffected — it's covered by the staticSerializerMethodAnalyzer(check_serializerscommand), which is unchanged aside from its issue type (see Added). The built-in analyzer count is now 7.
[2.0.1] - 2026-07-13¶
Added¶
.github/pull_request_template.md— PR template (summary, type, changelog entry, testing, checklist).
Changed¶
CONTRIBUTING.mdcodifies the PR workflow: all changes viafeat/*orfix/*branch → PR → review → squash-merge tomain. No direct pushes tomain.CHANGELOG.mdadopts Keep a Changelog format with an[Unreleased]section at the top. Every PR adds its entry here; on release,[Unreleased]is promoted to the version heading..github/pull_request_template.md: replaced the changelog-entry example (previously tied to this very fix, before it shipped) with a generic placeholder; splitbreakingout of the mutually-exclusive## Typelist into its ownBreaking change?question; dropped the redundant no-direct-to-main checkbox now that the branch ruleset enforces it.docs/getting-started/configuration.md: full rewrite. The previous example used dotted class paths forANALYZERSand dotted-pathREPORTERS, neither of which the code accepts; documented fictional keys (MIN_SEVERITY,QUERY_DOCTOR_ENABLED,EXCLUDE_PATHS,JSON_OUTPUT_DIR/HTML_OUTPUT_DIR); and impliedHTMLReporterworks viaREPORTERS, which it doesn't. Rewritten against the realDEFAULT_CONFIGand each key's call site, including three keys (STACK_TRACE_EXCLUDE,IGNORE_PATTERNS,QUERYIGNORE_PATH) that exist in defaults but aren't read by any code path yet.docs/guides/auto-fix.md: updated to describe the new safe/unsafe split and theast.parse()validation floor.
Fixed¶
fix_queries --applycould write broken code into your source files. The fixer edits the query's callsite line, but forn_plus_oneandfat_selectprescriptions that's frequently the in-loop attribute-access line, not the queryset definition — appending.select_related(...)or.only(...)there produced invalid or silently-wrong Python. This shipped in 2.0.0. If you ranfix_queries --applyon 2.0.0, check your diffs (git diffor the.bakfiles it created) for corrupted lines before trusting them.
As of 2.0.1, --apply only writes fixes for issue types verified safe
(queryset_eval, duplicate_query, missing_index) via a fixed
allowlist (fixer.AUTO_APPLIABLE_ISSUE_TYPES). n_plus_one, fat_select,
and drf_serializer are shown in the diff tagged [MANUAL FIX ONLY] and
refused at write time — apply those by hand. Before writing anything, the
candidate file content is also validated with ast.parse(); a fix that
would produce syntactically invalid Python is rejected instead of written
(this catches syntax errors only, not semantic correctness). fix_queries
--apply now exits nonzero if any fixes were skipped as unsafe or failed
validation, even when other fixes in the same run succeeded.
Post-patch, --apply performs exactly one real code transform
(queryset_eval) plus two # TODO-comment insertions (duplicate_query,
missing_index). n_plus_one and fat_select are dry-run only.
drf_serializer is never emitted by the runtime pipeline fix_queries
uses, so it never reaches the fixer at all.
[2.0.0] - 2026-03-21¶
Added¶
- QueryTurbo: SQL compilation cache with three-phase trust lifecycle
(UNTRUSTED → TRUSTED → POISONED). On cache miss, compiles and caches.
On untrusted hit, validates cached SQL against fresh
as_sql()output and promotes to TRUSTED afterVALIDATION_THRESHOLD(default 3) successful validations. On trusted hit, skipsas_sql()entirely and extracts params directly from the Query tree viaturbo/params.py. On mismatch, poisons the entry for the lifetime of the process (persists across cache clears triggered by migrations). - True SQL Compilation Skipping:
turbo/params.pyextracts params from the Django Query tree without callingas_sql(). Useslookup.as_sql(compiler, connection)per WHERE node for exact param transformations (handles__containswrapping,__isnulldiscarding, etc.) at a fraction of the cost of full SQL compilation. - Prepared Statement Bridge: Multi-database prepared statement support. Automatic protocol-level preparation on PostgreSQL + psycopg3 after a configurable hit-count threshold. Oracle implicit cursor caching. Graceful fallback (TypeError → permanent disable) on unsupported backends.
- AST SerializerMethodField Analyzer: Static analysis of DRF
get_<field>methods usingast.parse()to detect hidden N+1 queries at serialization time. Detects four patterns: related manager access, Model.objects calls, deep attribute chains, and for-loop queryset iteration. - Per-File Analysis:
--fileand--moduleflags oncheck_queriesanddiagnose_projectcommands for focused diagnosis via substring matching. - Benchmark Dashboard:
query_doctor_reportmanagement command generates standalone HTML report with Chart.js graphs showing cache hit rates, top optimized queries, and prepared statement statistics. - GitHub Actions CI Integration:
ci.githubmodule withformat_github_annotations()for inline PR diff annotations,generate_pr_comment()for Markdown PR summaries, andwrite_json_report()for CI consumption. Example workflow inexamples/github-actions/query-doctor.yml. - Baseline Snapshots:
baseline.pywithBaselineSnapshotclass for saving/loading issue snapshots. SHA-256 hashing ignores line numbers for stable identity across code movement.--save-baseline,--baseline, and--fail-on-regressionflags oncheck_queriesanddiagnose_project. - Smart Prescription Grouping:
grouping.pywithgroup_prescriptions()supportingfile_analyzer,root_cause, andviewstrategies.--groupflag oncheck_queriesanddiagnose_project. Console reporter supports grouped output mode. - Async-Safe Context Managers:
turbo_enabled()/turbo_disabled()now usecontextvars.ContextVarinstead ofthreading.local(), making them safe for ASGI deployments with concurrent coroutines. check_serializerscommand: Dedicated management command for AST-based DRF serializer analysis with--app,--file,--format, and--fail-onflags.- Post-migrate cache invalidation: Automatic cache clear on Django
post_migratesignal to prevent stale SQL after schema changes. - Fingerprint collision detection: Cache hit path validates SQL matches and poisons mismatched entries permanently.
__inlookup length in fingerprint: Different__inlist sizes produce different fingerprints, preventing SQL/param count mismatch.select_for_updatein fingerprint: Queries withFOR UPDATE,NOWAIT, andSKIP LOCKEDproduce distinct fingerprints.- Annotation source field fingerprinting: Annotations with the same name but different field targets produce different fingerprints.
Changed¶
- Minimum Python version remains 3.10
- All existing v1.x APIs remain backward compatible
- Version bumped to 2.0.0
- Context managers switched from
threading.local()tocontextvars.ContextVar - Cache entries now track
validated_count,trusted,poisonedstate - New config key:
VALIDATION_THRESHOLD(default 3) controls trust promotion
[1.0.3] - 2026-03-18¶
Fixed¶
- Missing Index analyzer now recommends
Meta.indexeswithmodels.Index()instead ofdb_index=True, following Django's official recommendation since 4.2 (fixes #1) - Auto-fix for missing indexes now generates
Meta.indexessuggestion instead ofdb_index=True _field_is_indexednow checksMeta.constraintsforUniqueConstraint(modern Django 4.2+ pattern) in addition tounique_together
Changed¶
- Full audit of all prescription texts across all 7 analyzers to align with Django 4.2–6.0 best practices
- Fat SELECT prescriptions now mention
.values()/.values_list()as alternatives when model instances aren't needed - N+1 prescriptions for
prefetch_relatednow mentionPrefetch()objects for advanced filtering scenarios - QuerySet evaluation prescriptions now mention
.iterator()for large querysets to reduce memory usage - Updated docs, README, and all affected tests to reflect new recommendation text
[1.0.2] - 2026-03-16¶
Fixed¶
- Fixed SVG terminal renders not displaying on GitHub (switched to absolute URLs)
- Removed Google Fonts @import from SVGs blocked by GitHub CSP
[1.0.1] - 2026-03-15¶
Changed¶
- Added SVG terminal renders to README for visual feature showcase
- Added Django 6.0 mention in README requirements
- Cleaned up committed pycache artifacts
- Updated .gitignore with additional exclusions
[1.0.0] - 2026-03-13¶
Added¶
Core Pipeline¶
- Query interception via
connection.execute_wrapper()— works withoutDEBUG=True - SQL fingerprinting with normalization and SHA-256 hashing
- Source code mapping with file:line references via stack trace analysis
- Django middleware with zero-config setup (one line in
MIDDLEWARE) diagnose_queries()context manager for targeted analysis@diagnoseand@query_budgetdecorators- Full configuration system via
QUERY_DOCTORDjango settings
Analyzers¶
- N+1 Detection — fingerprint-based grouping with FK pattern matching
- Duplicate Query Detection — exact-duplicate identification (same SQL and parameters, hashed and grouped)
- Missing Index Detection — WHERE/ORDER BY columns without indexes
- Fat SELECT Detection — flags
SELECT *when fewer columns suffice - QuerySet Evaluation — suggests
.count(),.exists(),.first()alternatives - DRF Serializer N+1 — detects missing prefetch in DRF views
Reporters¶
- Console — Rich terminal output with fallback to plain text
- JSON — structured output for CI/CD pipelines
- Log — Python logging integration
- HTML — standalone dashboard report
- OpenTelemetry — span and event export for observability stacks
Ecosystem¶
- Celery task support via
@diagnose_taskdecorator - Async Django/ASGI middleware support
- Custom analyzer plugin API via Python entry points
- Pytest plugin with
query_doctorfixture check_queriesmanagement command for CI analysisquery_budgetmanagement command for budget enforcement
Project-Wide Diagnosis¶
- diagnose_project management command — crawls all project URLs and generates app-wise health report
- Standalone HTML report with health scores, sortable app scoreboard, and per-URL prescription detail
- JSON report output for CI integration
- Admin dashboard integration showing latest project scan results
Auto-Fix & CI¶
- Auto-Fix Mode —
fix_queriesmanagement command applies diagnosed fixes with dry-run default and .bak backups - Diff-Aware CI —
--diffflag forcheck_queriesto analyze only files changed vs a git ref - .queryignore — project-level file to suppress known false positives by SQL pattern, file, callsite, or issue type
Monitoring¶
- Admin Dashboard — staff-only in-memory dashboard showing recent query diagnosis reports
- Query Complexity Scorer — regex-based SQL complexity analysis flagging excessive JOINs, subqueries, and OR chains
Developer Experience¶
- Every prescription includes severity, description, file:line, and exact code fix
- Zero required dependencies beyond Django
- Optional extras: Rich, Celery, OpenTelemetry
- Full type annotations with
py.typed(PEP 561) - CI matrix: Python 3.10-3.13 x Django 4.2-6.0