Skip to content

fix: schema-qualify the CREATE INDEX CONCURRENTLY wait query (#641) - #646

Merged
tianzhou merged 3 commits into
pgplex:mainfrom
shehjaddev:fix/issue-641-wait-schema
Oct 15, 2026
Merged

tianzhou merged 3 commits into
pgplex:mainfrom
shehjaddev:fix/issue-641-wait-schema

Conversation

@shehjaddev

Copy link
Copy Markdown
Contributor

Fixes #641

Summary

After CREATE INDEX CONCURRENTLY, the plan emits a pgschema:wait query that polls pg_index.indisvalid. That query matched the index by pg_class.relname only. checkWaitStatus reads the first row it gets back, so an invalid index with the same name in another schema could stall apply indefinitely. This happens when a concurrent build failed and left an index behind in a sibling tenant schema.

generateIndexWaitQueryWithName now takes the schema and adds AND c.relnamespace = '<schema>'::regnamespace. All three callers pass the index's schema: the plain create, the change via _pgschema_new, and the diff-driven rewrite.

The plan fixtures that embed the wait query have been regenerated with go test ./cmd -run TestPlanAndApply -generate. The issue_295_pgvector_typmod fixture is skipped without pgvector, so it was updated by hand to match. Unrelated pgschema_version churn from regeneration was reverted.

Validation

Passed locally with Go 1.24.7 on Linux, embedded PostgreSQL 18:

go vet ./...
gofmt -l <changed files>      # clean
go test -count=1 ./...        # all packages ok

New tests:

  • internal/plan/rewrite_test.go: the wait SQL is schema-qualified, and it is wired through generateIndexRewrite.
  • cmd/issue_641_integration_test.go: leaves an invalid tenant_a.items_sku_idx after a failed concurrent unique build, then:
    • shows that the name-only query returns done=false, which is the hang;
    • shows that the qualified query returns done=true for tenant_b;
    • runs plan + apply end to end on tenant_b with the invalid peer present. It finishes in about 0.2s.

Scope

This only changes the wait query. It does not detect or clean up invalid leftover indexes. A same-schema invalid index of the same name is still a separate case, and CREATE INDEX CONCURRENTLY IF NOT EXISTS would skip it.

)

The wait directive emitted after CREATE INDEX CONCURRENTLY matched the
index by pg_class.relname only. checkWaitStatus reads the first row, so an
invalid index with the same name in another schema (e.g. a failed
concurrent build left behind in a sibling tenant schema) made apply wait
forever.

Filter the wait query by relnamespace as well, for the create, change, and
rewrite paths, and update the plan fixtures that embed the wait query.
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Fixes index wait query to be schema-qualified.

Fix quoted schema names before merging; the new wait predicate can stop otherwise valid index migrations.

Findings

  1. P1 Quoted schemas break index waits ▶
  2. P2 Timeout cannot stop hung apply ▶

Summary

The PR limits concurrent-index waits to the index's schema.

  • Concurrent-index waits check the schema of the index they track.

Scope acknowledged by shehjaddev: detecting or cleaning up same-schema invalid indexes is intentionally excluded; CREATE INDEX CONCURRENTLY IF NOT EXISTS can still skip those indexes.

Reviews (1) · Last reviewed commit: "fix: schema-qualify the CREATE INDEX CON..." · Reviewed by Greptile

Comment thread internal/plan/rewrite.go Outdated
Comment thread cmd/issue_641_integration_test.go Outdated
The '<schema>'::regnamespace cast case-folds unquoted names, so a
mixed-case schema such as "TenantB" was looked up as tenantb, and an
apostrophe in the schema name produced invalid SQL. Join pg_namespace and
compare nspname (and relname) against properly escaped string literals.

Make the issue pgplex#641 integration test fail after a deadline instead of
hanging, cover a mixed-case quoted schema, and regenerate the plan
fixtures that embed the wait query.
Comment thread cmd/issue_641_integration_test.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused change consistently filters all three rewrite paths by target schema, with matching tests and fixtures and no unresolved blocking issues.

0 open findings

What changed in this PR

Fixes #641 by limiting concurrent-index wait queries to the target schema, preventing invalid same-named indexes in other schemas from blocking apply.

Changes:

  • Pass the schema through all three index rewrite paths and filter by exact namespace name.
  • Add tests for schema filtering, mixed-case names, and apostrophe escaping.
  • Update expected plan outputs to include schema-qualified waits.
File Description
testdata/​diff/​online/​issue_509_index_rebuild_dropped_column/​plan.txt Qualify rebuilt-index wait.
testdata/​diff/​online/​issue_509_index_rebuild_dropped_column/​plan.sql Qualify rebuilt-index wait.
testdata/​diff/​online/​issue_509_index_rebuild_dropped_column/​plan.json Qualify rebuilt-index wait.
testdata/​diff/​online/​issue_418_partitioned_parent_index/​plan.txt Qualify partition-index wait.
testdata/​diff/​online/​issue_418_partitioned_parent_index/​plan.sql Qualify partition-index wait.
testdata/​diff/​online/​issue_418_partitioned_parent_index/​plan.json Qualify partition-index wait.
testdata/​diff/​online/​issue_286_reserved_keyword_quoting/​plan.txt Qualify quoted-index wait.
testdata/​diff/​online/​issue_286_reserved_keyword_quoting/​plan.sql Qualify quoted-index wait.
testdata/​diff/​online/​issue_286_reserved_keyword_quoting/​plan.json Qualify quoted-index wait.
testdata/​diff/​online/​alter_materialized_view_index/​plan.txt Qualify replacement-index wait.
testdata/​diff/​online/​alter_materialized_view_index/​plan.sql Qualify replacement-index wait.
testdata/​diff/​online/​alter_materialized_view_index/​plan.json Qualify replacement-index wait.
testdata/​diff/​online/​alter_composite_index/​plan.txt Qualify replacement-index waits.
testdata/​diff/​online/​alter_composite_index/​plan.sql Qualify replacement-index waits.
testdata/​diff/​online/​alter_composite_index/​plan.json Qualify replacement-index waits.
testdata/​diff/​online/​add_unique_multi_column_index/​plan.txt Qualify unique-index wait.
testdata/​diff/​online/​add_unique_multi_column_index/​plan.sql Qualify unique-index wait.
testdata/​diff/​online/​add_unique_multi_column_index/​plan.json Qualify unique-index wait.
testdata/​diff/​online/​add_partial_index/​plan.txt Qualify partial-index wait.
testdata/​diff/​online/​add_partial_index/​plan.sql Qualify partial-index wait.
testdata/​diff/​online/​add_partial_index/​plan.json Qualify partial-index wait.
testdata/​diff/​online/​add_materialized_view_index/​plan.txt Qualify materialized-view index wait.
testdata/​diff/​online/​add_materialized_view_index/​plan.sql Qualify materialized-view index wait.
testdata/​diff/​online/​add_materialized_view_index/​plan.json Qualify materialized-view index wait.
testdata/​diff/​online/​add_functional_index/​plan.txt Qualify functional-index wait.
testdata/​diff/​online/​add_functional_index/​plan.sql Qualify functional-index wait.
testdata/​diff/​online/​add_functional_index/​plan.json Qualify functional-index wait.
testdata/​diff/​online/​add_composite_index/​plan.txt Qualify composite-index waits.
testdata/​diff/​online/​add_composite_index/​plan.sql Qualify composite-index waits.
testdata/​diff/​online/​add_composite_index/​plan.json Qualify composite-index waits.
testdata/​diff/​migrate/​v4/​plan.txt Qualify migration index waits.
testdata/​diff/​migrate/​v4/​plan.sql Qualify migration index waits.
testdata/​diff/​migrate/​v4/​plan.json Qualify migration index waits.
testdata/​diff/​migrate/​v2/​plan.txt Qualify migration index waits.
testdata/​diff/​migrate/​v2/​plan.sql Qualify migration index waits.
testdata/​diff/​migrate/​v2/​plan.json Qualify migration index waits.
testdata/​diff/​dependency/​issue_535_domain_depends_on_table/​plan.txt Formatting-only fixture refresh.
testdata/​diff/​dependency/​issue_505_drop_trigger_function_with_table/​plan.txt Formatting-only fixture refresh.
testdata/​diff/​dependency/​issue_308_view_select_star_column_reorder/​plan.txt Qualify recreated-index wait.
testdata/​diff/​dependency/​issue_308_view_select_star_column_reorder/​plan.sql Qualify recreated-index wait.
testdata/​diff/​dependency/​issue_308_view_select_star_column_reorder/​plan.json Qualify recreated-index wait.
testdata/​diff/​create_trigger/​add_trigger/​plan.txt Formatting-only fixture refresh.
testdata/​diff/​create_table/​issue_591_alter_generated_column/​plan.txt Qualify generated-column index waits.
testdata/​diff/​create_table/​issue_591_alter_generated_column/​plan.sql Qualify generated-column index waits.
testdata/​diff/​create_table/​issue_591_alter_generated_column/​plan.json Qualify generated-column index waits.
testdata/​diff/​create_table/​issue_506_fk_depends_on_new_unique/​plan.txt Qualify prerequisite unique-index wait.
testdata/​diff/​create_table/​issue_506_fk_depends_on_new_unique/​plan.sql Qualify prerequisite unique-index wait.
testdata/​diff/​create_table/​issue_506_fk_depends_on_new_unique/​plan.json Qualify prerequisite unique-index wait.
testdata/​diff/​create_table/​issue_295_pgvector_typmod/​plan.txt Qualify vector-index wait.
testdata/​diff/​create_table/​issue_295_pgvector_typmod/​plan.sql Qualify vector-index wait.
testdata/​diff/​create_table/​issue_295_pgvector_typmod/​plan.json Qualify vector-index wait.
testdata/​diff/​create_index/​alter_index_null_order/​plan.txt Qualify replacement-index wait.
testdata/​diff/​create_index/​alter_index_null_order/​plan.sql Qualify replacement-index wait.
testdata/​diff/​create_index/​alter_index_null_order/​plan.json Qualify replacement-index wait.
testdata/​diff/​create_index/​add_index_with_reloptions/​plan.txt Qualify index waits.
testdata/​diff/​create_index/​add_index_with_reloptions/​plan.sql Qualify index waits.
testdata/​diff/​create_index/​add_index_with_reloptions/​plan.json Qualify index waits.
internal/​plan/​rewrite.go Filter waits by schema and escape names.
internal/​plan/​rewrite_test.go Test schema propagation and exact-name escaping.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tianzhou tianzhou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the contribution

@tianzhou
tianzhou merged commit df321e6 into pgplex:main Oct 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pgschema:wait query is not schema-qualified: apply hangs when another schema has an invalid index of the same name

3 participants