Repository navigation
migrate: show the index's live usage when refusing a DROP INDEX - #277
Conversation
--accept-blocking accepts the lock, not the loss of the index. A dropped index the application still reads through is a common mistake, so the refusal lists the checks to make before acknowledging it.
The refusal asks the operator to confirm the index is unused; it now carries the connected server's idx_scan, stats_reset, last scan and uniqueness as evidence, as a typed index_usage field.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Review 1/2 of 83f16f3: 1 blocking, 2 non-blocking. I read the whole diff at The change is well scoped: the read is one bounded, read-only transaction keyed on the index OID the owning-table lookup already resolved, a failed read returns an error with nothing executed (fail closed), the refusal itself is untouched, and the evidence is reported rather than judged. The one defect is that the evidence is wrong for a kind of index the same code path explicitly accepts. Invariants: extends RF-10 (the refusal still stands and still names the acknowledgement; it now also carries evidence), upholds CO-9 (the new query is Blocking1. A partitioned index reports Reproduced on PostgreSQL 17 with the built binary: a table partitioned by range with
Suggested fix: for relkind Test case: fails on 83f16f3package preflight_test
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/block/pg-sprite/internal/testutil"
"github.com/block/pg-sprite/pkg/preflight"
)
// The scans of a partitioned table's index are recorded on the partitions'
// indexes, so the usage read of the parent index must report them.
func TestReadIndexUsageCountsScansOfAPartitionedIndexsPartitions(t *testing.T) {
f := newUsageFixture(t, testutil.StartPostgres(t))
f.exec(t, fmt.Sprintf(`
CREATE TABLE %[1]s.events (id int, ts timestamptz NOT NULL, k int) PARTITION BY RANGE (ts);
CREATE TABLE %[1]s.events_2026 PARTITION OF %[1]s.events
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
CREATE INDEX events_k_idx ON %[1]s.events (k);
INSERT INTO %[1]s.events SELECT g, '2026-03-01', g FROM generate_series(1, 2000) g;
ANALYZE %[1]s.events`, f.schema))
f.exec(t, "SET enable_seqscan = off")
for range 3 {
f.exec(t, fmt.Sprintf("SELECT id FROM %s.events WHERE k = 5", f.schema))
}
f.exec(t, "RESET enable_seqscan")
f.publish(t)
parent := f.indexOID(t, "events_k_idx")
var u preflight.IndexUsage
require.Eventually(t, func() bool {
var err error
u, err = preflight.ReadIndexUsage(t.Context(), f.pool, parent)
require.NoError(t, err)
return u.IdxScans > 0
}, statsVisible, 100*time.Millisecond, "the partition's scans must reach the parent index's usage")
assert.EqualValues(t, 3, u.IdxScans)
}
Non-blocking2. The window the detail states can be longer than the window the count covers. 3. Most of the new behavior is not pinned by a test. The mutation table below shows the evidence sentence, every caveat in it, and the error path surviving. The acceptance test asserts Verified
This review was generated by Claude Code (claude-sonnet-5-5). |
|
🤖 Review 2/2 of 83f16f3: OSS adoption and SchemaBot integration lenses, 0 blocking, 3 non-blocking. This is the second of two comments; the correctness pass, with its one blocking finding about partitioned indexes, is the one above. The seam an adapter binds to moves only additively: a new exported BlockingNone. Non-blocking1. 2. The 3. A failed read changes the outcome class of a refusal, and the error is untyped. On a statistics-read failure the refusal (exit 2, Verified
This review was generated by Claude Code (claude-sonnet-5-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Stamping with comments: 1 blocking, 5 non-blocking (see the two review comments above).
This stamp was left by Claude Code (claude-sonnet-5-5).
A partitioned index has no statistics row, so its usage read as a clean zero. Also reports saved track_counts settings, states the creation-time window caveat, and emits the evidence in UTC.
|
🤖 Delta review of d063f92 (since 83f16f3): 0 blocking, 1 non-blocking. The head moved by one commit on top of The blocking finding is resolvedThe partitioned index no longer reads as a clean zero. Evidence, run on the head:
The other earlier points
Non-blocking1. The new missing-statistics branch is not pinned. I removed the InvariantsExtends RF-10 (the refusal's evidence is now correct for a partitioned index), upholds CO-9 (every function, operator, relation, and type in the new CTE is Verified
This review was generated by Claude Code (claude-sonnet-5-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Stamping with comments: 0 blocking, 1 non-blocking (see the delta review comment above). The earlier blocking finding on partitioned indexes is resolved.
This stamp was left by Claude Code (claude-sonnet-5-5).
A live server lists every leaf index, so the fail-closed check is tested as a pure helper rather than through ReadIndexUsage.
|
🤖 Adversarial review response — created by Kiran's code review agent (Amp, Claude Opus 4.6) — pull/277, follow-up commit All three comments are answered. The blocking partitioned-index finding and every non-blocking finding are fixed: the first two comments' findings in
Source: block/pg-sprite#277, review comments 6568238539 and 6568239449 at head |
The
destructive-changerefusal of an acceptedDROP INDEXnow carries the connected server's live usage evidence for the index, as a typedindex_usagefield on the verdict and a line in its text.Stacked on #276; retarget to
mainonce it merges.Why
#276 refuses a
DROP INDEXunder--accept-blockinguntil--allow-destructivenames the table, and its detail asks the operator to confirm the index is unused. pg-sprite already holds a connection to the writer, so it can show the first piece of that evidence instead of only describing the query to run. It stays evidence, not a gate: one server's counters cannot see a replica's reads, so the decision remains the operator's.What
preflight.ReadIndexUsage(ctx, pool, indexOID): one bounded, read-only, lock-free read of the index'sidx_scan,last_idx_scan(PostgreSQL 16+), uniqueness, the database'sstats_reset,track_counts, and the server clock, from one statistics snapshot. Keyed on the OID the owning-table lookup already resolved.verdict.IndexUsage(index_usage):idx_scan(always present, so a 0 is visible),last_idx_scanandstats_reset(omitted when the server recorded none),read_at,unique,track_counts. The text form gains ausage:line, and the refusal detail states the count and its window.index_usage.idx_scanon the refusal.Before / after
The same refused drop of an index the application scanned 1,284 times since the last reset. Before, the operator is told how to check; after, the check's answer for this server is in the refusal.
On PostgreSQL 14 and 15
last_idx_scanis absent; withtrack_countsoff the text says the count proves nothing.🤖 Generated with Amp (Claude Opus 4.6)