<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd" -->

---
title: The tests passed. The plan didn't. | daily.dev
description: RegreSQL 2.0 extends query regression testing beyond row-level correctness to catch plan regressions — cases where queries return the right rows but via a...
canonical: https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The tests passed. The plan didn't. | daily.dev
og:description: RegreSQL 2.0 extends query regression testing beyond row-level correctness to catch plan regressions — cases where queries return the right rows but via a...
og:url: https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd
og:image: https://api.daily.dev/og/posts/NKf8H9xZD.png
og:image:alt: The tests passed. The plan didn't.
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The tests passed. The plan didn't.

**[Planet PostgreSQL](https://daily.dev/sources/planet-postgresql)** · 9 min read · 1 upvotes · 0 comments

## Summary

RegreSQL 2.0 extends query regression testing beyond row-level correctness to catch plan regressions — cases where queries return the right rows but via a worse execution plan. It addresses the core problem that dev databases with small datasets produce misleading query plans by injecting production statistics (using PostgreSQL 18's pg_dump --statistics-only) so EXPLAIN reflects real table distributions. Instead of gating on estimated costs, it measures actual buffer reads, cardinality errors (q-error), and disk spills. It also handles statistical noise carefully, refusing to report cross-version regressions when plan differences stem from ANALYZE sampling variance rather than real planner changes. The tool integrates with CI via JUnit/GitHub Actions output and exits non-zero on plan failures, making plan drift a reviewable, blockable event rather than a production surprise.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://postgr.es/p/9pn>

## Questions this post answers

### How does RegreSQL 2.0 detect a query plan regression that row-output testing would miss?

It compares the actual execution plan and measured buffers against a stored baseline, not just the returned rows. For example, dropping an index can silently switch a query from a bitmap index scan to a sequential scan, reading 35 times more buffers, even though the output rows are unchanged; RegreSQL 2.0 flags this as a failing test by comparing buffer counts and reporting the plan node change.

_Teams hardening PostgreSQL migrations can track plan-regression tooling like this through daily.dev._

### How can I test PostgreSQL query plans locally so they match what production would actually run?

Inject production's planner statistics into a local database using PostgreSQL 18's pg_dump --statistics-only feature, then run EXPLAIN against that injected data instead of a small dev dataset. RegreSQL 2.0 uses `regresql test --stats production-stats.sql` so a laptop with only a handful of rows produces the same query plan the planner would choose against the real table's row counts and histograms.

_Developers verifying query plans before deploy can follow tooling like this via daily.dev._

### Why does gating query regression tests on estimated cost instead of measured execution behavior cause false positives or missed regressions?

Cost estimates are the planner's opinion and drift most exactly when a plan is about to fail, since a bad plan usually starts as a bad cardinality estimate. RegreSQL 2.0 instead gates on measured actuals (buffers read, disk spills, rows actually processed) and the q-error, the ratio between predicted and actual row counts, so a widening cardinality error signals danger before the cost number does.

_Engineers debugging flaky plan regressions can follow measurement-based testing approaches on daily.dev._

## Similar posts on daily.dev

- [RegreSQL: Regression Testing for PostgreSQL Queries](https://daily.dev/posts/regresql-regression-testing-for-postgresql-queries-f2g2bzl1k) · Hacker News · 4 upvotes · 0 comments
- [Production query plans without production data](https://daily.dev/posts/production-query-plans-without-production-data-ja6qayvju) · Lobsters · 0 upvotes · 0 comments
- [Resolving query plan regressions after a MySQL engine upgrade](https://daily.dev/posts/resolving-query-plan-regressions-after-a-mysql-engine-upgrade-7oliv3023) · AWS Database Blog · 0 upvotes · 0 comments
- [Doltgres Reaches 99% Compliance on SQL Logic Tests](https://daily.dev/posts/doltgres-reaches-99-compliance-on-sql-logic-tests-hhnymgooh) · DoltHub Blog · 0 upvotes · 0 comments

---

Tags: [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The tests passed. The plan didn't.","url":"https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd"},"datePublished":"2026-07-12T18:18:11.057Z","dateModified":"2026-09-13T21:53:25.631Z","description":"RegreSQL 2.0 extends query regression testing beyond row-level correctness to catch plan regressions — cases where queries return the right rows but via a...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/de7676b52d28c903b81db442d5b9167a?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/de7676b52d28c903b81db442d5b9167a?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Planet PostgreSQL","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Planet PostgreSQL","logo":"https://media.daily.dev/image/upload/logos/placeholder.jpg","url":"https://daily.dev/sources/planet-postgresql"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"postgresql","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Planet PostgreSQL","item":"https://daily.dev/sources/planet-postgresql"},{"@type":"ListItem","position":3,"name":"The tests passed. The plan didn't."}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-tests-passed-the-plan-didn-t--nkf8h9xzd#faq","mainEntity":[{"@type":"Question","name":"How does RegreSQL 2.0 detect a query plan regression that row-output testing would miss?","acceptedAnswer":{"@type":"Answer","text":"It compares the actual execution plan and measured buffers against a stored baseline, not just the returned rows. For example, dropping an index can silently switch a query from a bitmap index scan to a sequential scan, reading 35 times more buffers, even though the output rows are unchanged; RegreSQL 2.0 flags this as a failing test by comparing buffer counts and reporting the plan node change. Teams hardening PostgreSQL migrations can track plan-regression tooling like this through daily.dev."}},{"@type":"Question","name":"How can I test PostgreSQL query plans locally so they match what production would actually run?","acceptedAnswer":{"@type":"Answer","text":"Inject production's planner statistics into a local database using PostgreSQL 18's pg_dump --statistics-only feature, then run EXPLAIN against that injected data instead of a small dev dataset. RegreSQL 2.0 uses `regresql test --stats production-stats.sql` so a laptop with only a handful of rows produces the same query plan the planner would choose against the real table's row counts and histograms. Developers verifying query plans before deploy can follow tooling like this via daily.dev."}},{"@type":"Question","name":"Why does gating query regression tests on estimated cost instead of measured execution behavior cause false positives or missed regressions?","acceptedAnswer":{"@type":"Answer","text":"Cost estimates are the planner's opinion and drift most exactly when a plan is about to fail, since a bad plan usually starts as a bad cardinality estimate. RegreSQL 2.0 instead gates on measured actuals (buffers read, disk spills, rows actually processed) and the q-error, the ratio between predicted and actual row counts, so a widening cardinality error signals danger before the cost number does. Engineers debugging flaky plan regressions can follow measurement-based testing approaches on daily.dev."}}]}
```

