Uh oh!
There was an error while loading. Please reload this page.
fix: cross-schema FK support via ignore stubs (#548) - #549
Conversation
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>
There was a problem hiding this comment.
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) and42P01(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
| File | Description |
|---|---|
| internal/postgres/external.go | Adds cross-schema reference hinting to apply failures when using an external plan DB. |
| internal/postgres/embedded.go | Adds cross-schema reference hinting to apply failures when using the embedded plan DB. |
| internal/postgres/desired_state.go | Introduces hintOnSQLState helper and hintCrossSchemaReference for schema/relation missing SQLSTATEs. |
| internal/postgres/desired_state_test.go | Adds unit tests validating new cross-schema hint behavior and wrapping behavior via enhanceApplyError. |
| docs/cli/plan-db.mdx | Documents cross-schema FK handling, including the “stub schema/table” workaround and external plan DB option. |
| docs/cli/ignore.mdx | Clarifies 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.
Uh oh!
There was an error while loading. Please reload this page.
Greptile SummaryThe PR adds targeted guidance for missing schemas and relations encountered while applying desired-state SQL and documents why ignore filtering cannot bypass that validation.
Confidence Score: 4/5The 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 Files Needing Attention: internal/postgres/external.go, docs/cli/plan-db.mdx, internal/postgres/desired_state.go Important Files Changed
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]
Reviews (1): Last reviewed commit: "fix: hint missing schema/relation when p..." | Re-trigger Greptile |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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>
There was a problem hiding this comment.
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
stubsvia 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
.pgschemaignorecannot 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 byApplySchema.
// .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
ExtractForeignKeyTargetstreats "public" as a keyword that can’t be a table name afterREFERENCES, butpublicis a valid unquoted identifier in PostgreSQL. This will incorrectly skip legitimateREFERENCES 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,
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
…a newlines Co-authored-by: Tianzhou <tianzhou@users.noreply.github.com>
There was a problem hiding this comment.
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
Uh oh!
There was an error while loading. Please reload this page.
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
REFERENCES auth.users(...)in your desired SQL on managed tables..pgschemaignore: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.auth.users, plan will not create/drop it.Follow-up review fixes
strings.Builderwhen accumulating generated stubs (avoid repeated string reallocation)CREATE TABLE auth.usershintCrossSchemaReferenceto match ignore-based stubbing behaviorpublicas a valid unqualified referenced table name in FK extractor (with regression test)When fallbacks are still needed
CREATE TABLEstub in schema file, or external plan DB.pgschemaignorefirst, then fallbacksDocs
[schemas]section and Supabase exampleTest plan
go test ./ir ./internal/postgres ./cmd/utilgo test ./internal/postgres -run 'TestExtractForeignKeyTargets|TestHintCrossSchemaReference'go test ./cmd -run TestIgnore(includingTestIgnoreCrossSchemaForeignKey)