Skip to content

fix: cross-schema FK support via ignore stubs (#548) - #549

Merged
tianzhou merged 7 commits into
mainfrom
cursor/issue-548-cross-schema-hint-f3e0
Aug 18, 2026
Merged

fix: cross-schema FK support via ignore stubs (#548)#549
tianzhou merged 7 commits into
mainfrom
cursor/issue-548-cross-schema-hint-f3e0

Conversation

@tianzhou

@tianzhoutianzhou commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes#548: cross-schema foreign keys to unmanaged tables (for example Supabase auth.users) work with the default embedded plan database — no manual stub in schema files and no external plan DB required for the common case.

How it works

  1. Keep REFERENCES auth.users(...) in your desired SQL on managed tables.
  2. Ignore the external schema or table in .pgschemaignore:
[schemas]
patterns = ["auth"]
# or
[tables]
patterns = ["auth.users"]
  1. During plan, pgschema clones a structural stub of each ignored FK target from the target database (columns + PRIMARY KEY / UNIQUE) into the temporary plan instance so PostgreSQL can validate the FK.
  2. The FK on your managed table is still diffed and applied normally. The stub is not managed — dump omits auth.users, plan will not create/drop it.

Follow-up review fixes

  • Use strings.Builder when accumulating generated stubs (avoid repeated string reallocation)
  • Tighten integration assertion to specifically detect accidental CREATE TABLE auth.users
  • Update outdated comment in hintCrossSchemaReference to match ignore-based stubbing behavior
  • Allow public as a valid unqualified referenced table name in FK extractor (with regression test)

When fallbacks are still needed

  • Referenced table does not exist on the target → manual CREATE TABLE stub in schema file, or external plan DB
  • PostgreSQL extensions → external plan DB with extensions installed
  • Plan-time error hints now point at .pgschemaignore first, then fallbacks

Docs

  • Ignore: new [schemas] section and Supabase example
  • Plan DB: ignore is recommended; manual stub / external plan DB are fallbacks

Test plan

  • go test ./ir ./internal/postgres ./cmd/util
  • go test ./internal/postgres -run 'TestExtractForeignKeyTargets|TestHintCrossSchemaReference'
  • go test ./cmd -run TestIgnore (including TestIgnoreCrossSchemaForeignKey)
Open in WebOpen in Cursor

Plan applies desired-state SQL before .pgschemaignore filtering, so
REFERENCES auth.users fails on the embedded plan database. Hint 3F000
and 42P01 toward a stub CREATE SCHEMA/TABLE or an external plan database,
and document that ignore cannot skip this check.
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
@tianzhou
tianzhou marked this pull request as ready for review August 18, 2026 06:10
CopilotAI lite review requested due to automatic review settings August 18, 2026 06:10

CopilotAI 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.

Pull request overview

This PR improves the plan/desired-state apply error experience when user SQL contains cross-schema references (e.g., REFERENCES auth.users) that fail in the embedded/external plan database because the referenced schema/relation doesn’t exist. It adds targeted SQLSTATE-based hints for missing schema/relation errors and updates documentation to clarify why .pgschemaignore can’t bypass these failures and how to work around them.

Changes:

  • Add SQLSTATE-driven hinting for 3F000 (missing schema) and 42P01 (missing relation) during desired SQL apply.
  • Refactor SQLSTATE hinting into a shared helper and add unit tests for the new hint behavior.
  • Update CLI docs to explain cross-schema FK handling and why ignore filtering doesn’t prevent apply-time dependency checks.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
internal/postgres/external.goAdds cross-schema reference hinting to apply failures when using an external plan DB.
internal/postgres/embedded.goAdds cross-schema reference hinting to apply failures when using the embedded plan DB.
internal/postgres/desired_state.goIntroduces hintOnSQLState helper and hintCrossSchemaReference for schema/relation missing SQLSTATEs.
internal/postgres/desired_state_test.goAdds unit tests validating new cross-schema hint behavior and wrapping behavior via enhanceApplyError.
docs/cli/plan-db.mdxDocuments cross-schema FK handling, including the “stub schema/table” workaround and external plan DB option.
docs/cli/ignore.mdxClarifies that ignore cannot make plan succeed if the desired SQL itself references missing cross-schema objects.

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

Comment threadinternal/postgres/desired_state.go
@greptile-apps

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds targeted guidance for missing schemas and relations encountered while applying desired-state SQL and documents why ignore filtering cannot bypass that validation.

  • Adds reusable SQLSTATE-based hinting for embedded and external plan databases.
  • Documents inline stubs and pre-populated external plan databases as cross-schema-reference workarounds.
  • Adds unit coverage for matching, wrapped, and unrelated PostgreSQL errors.

Confidence Score: 4/5

The external-plan-database hint should be corrected before merging because its inline-stub workaround can leave stale cross-schema objects that affect later plan runs.

External desired SQL executes qualified stubs outside the temporary schema, while cleanup removes only that temporary schema; repeated IF NOT EXISTS declarations can therefore validate against stale persistent definitions.

Files Needing Attention: internal/postgres/external.go, docs/cli/plan-db.mdx, internal/postgres/desired_state.go

Important Files Changed

FilenameOverview
internal/postgres/desired_state.goIntroduces generalized SQLSTATE hint wrapping and cross-schema diagnostics; the surrounding parser comment also contains accidental curly-quote substitutions.
internal/postgres/external.goAdds a hint that recommends inline stubs without accounting for their persistence outside the external provider's temporary schema.
internal/postgres/embedded.goAdds the cross-schema hint to embedded desired-state application, where inline stubs disappear with the disposable instance.
internal/postgres/desired_state_test.goCovers matching SQLSTATEs, wrapped PostgreSQL errors, and pass-through behavior for unrelated errors.
docs/cli/plan-db.mdxExplains cross-schema workarounds but does not constrain inline stubs to avoid stale persistent objects when combined with an external plan database.
docs/cli/ignore.mdxCorrectly clarifies that ignore filtering occurs after desired SQL has already been applied.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
SQL[Desired-state SQL] --> Rewrite[Rewrite managed-schema qualifiers]
Rewrite --> Apply{Plan database}
Apply -->|Embedded| TempEmbedded[Temporary embedded instance]
Apply -->|External| TempSchema[Temporary schema in persistent database]
TempEmbedded --> Error[PostgreSQL error]
TempSchema --> Error
Error --> State{SQLSTATE}
State -->|42704 / 42883| Extension[Extension hint]
State -->|3F000 / 42P01| CrossSchema[Cross-schema hint]
TempSchema --> Stub[Qualified inline stub]
Stub --> Persistent[Object persists outside temporary schema]
Loading

Reviews (1): Last reviewed commit: "fix: hint missing schema/relation when p..." | Re-trigger Greptile

Comment threadinternal/postgres/external.go Outdated
Comment threadinternal/postgres/desired_state.go
tianzhouand others added 4 commits August 17, 2026 23:26
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
When desired SQL references an ignored table (auth.users, [schemas]
auth, or same-schema ignored tables) that is not defined in the file,
clone a structural stub from the target database before applying SQL
to the plan instance. Ignore now works for Supabase-style FKs without
a hand-written CREATE TABLE stub.
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
Clarify that Supabase-style REFERENCES auth.users works with the default
embedded plan DB when the schema is ignored and the table exists on target.
Manual stubs and external plan DB are documented as fallbacks only.
Update plan-time hints to point at .pgschemaignore first.
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
@cursorcursorBot changed the title fix: hint missing schema/relation when plan applies desired SQL (#548)fix: cross-schema FK support via ignore stubs (#548)Aug 18, 2026
@tianzhou
tianzhou requested a lite review from CopilotAugust 18, 2026 07:16

CopilotAI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

cmd/plan/ignore_stubs.go:68

  • Building stubs via repeated string concatenation (stubs += ddl) allocates a new string each iteration and can become quadratic if many FK targets need stubs. Accumulating into a byte slice (or builder) avoids the repeated allocations.
	var stubs string
for _, ref := range toStub {
ddl, err := ir.BuildTableStubSQL(ctx, conn, ref.Schema, ref.Table, targetSchema)

cmd/ignore_integration_test.go:1952

  • This assertion can false-positive because it only checks that the output contains both "CREATE TABLE" (e.g., for some other table) and "auth.users" (from the FK reference). It should specifically assert that the plan output does not create auth.users.
 if strings.Contains(output, "CREATE TABLE") && strings.Contains(output, "auth.users") {
t.Errorf("plan should not create auth.users; got:\n%s", output)
}

internal/postgres/desired_state.go:531

  • The comment here says .pgschemaignore cannot help with cross-schema references, but this PR adds ignore-based plan-time stubbing specifically to make that case work. Update this comment to avoid contradicting the new behavior and the hints emitted by ApplySchema.
// .pgschemaignore cannot help: plan applies the SQL before ignore filtering,
// so PostgreSQL still requires the referenced objects to exist. Stub them in
// the desired SQL, or use a plan database that already has them.

internal/postgres/fk_refs.go:23

  • ExtractForeignKeyTargets treats "public" as a keyword that can’t be a table name after REFERENCES, but public is a valid unquoted identifier in PostgreSQL. This will incorrectly skip legitimate REFERENCES public(...) foreign keys. Removing it avoids false negatives while still filtering the intended GRANT pattern via the existing "on" keyword.
	"where": true,
"set": true,
"public": true,
"all": true,

Comment threadir/stub.go
cursoragentand others added 2 commits August 18, 2026 07:31
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
…a newlines
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>

CopilotAI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/postgres/desired_state.go:135

  • The comment describing E'...' escape-string parsing appears to have been corrupted (smart quotes) and no longer reflects valid SQL syntax (e.g., "'it”s'" / "E'content\”"). This is misleading for future maintenance and should be restored to the correct '' (doubled-quote) examples.
// Limitation: E'...' escape-string syntax uses backslash-escaped quotes (E'it\'s')
// rather than doubled quotes ('it”s'). This parser only recognises the ” form.
// With E'content\”, a backslash-escaped quote may cause the parser to mistrack

@tianzhou
tianzhou merged commit 04b73fc into mainAug 18, 2026
2 checks passed
Sign up for freeto 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.

Error with a FK on a schema that's not dumped

3 participants

@tianzhou@cursoragent