Skip to content

Pd/feat/plan multi schema#431

Closed
pedroramosdiehl wants to merge 2 commits into
pgplex:mainfrom
pedroramosdiehl:pd/feat/plan_multi_schema
Closed

Pd/feat/plan multi schema#431
pedroramosdiehl wants to merge 2 commits into
pgplex:mainfrom
pedroramosdiehl:pd/feat/plan_multi_schema

Conversation

@pedroramosdiehl
Copy link
Copy Markdown

No description provided.

- Updated the `README.md` to include instructions for using multiple schemas with the `pgschema plan` command, detailing the behavior and caveats.
- Modified the `plan.go` command to accept a comma-separated list of schemas, allowing for comparison across multiple PostgreSQL namespaces.
- Enhanced the `GeneratePlan` function to handle multiple schemas, ensuring correct introspection and DDL generation.
- Added new utility functions for parsing schema lists and computing fingerprints for multiple schemas.
- Updated related documentation and tests to reflect these changes.

This update improves the flexibility of schema management in migration plans, accommodating more complex database structures.
- Implemented the GetObjectName method for the Schema type, allowing it to return the schema's name.
- This addition enhances the consistency of object name retrieval across different types in the IR package.
Copilot AI review requested due to automatic review settings May 13, 2026 19:55
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR adds multi-schema planning support for PostgreSQL schemas. The main changes are:

  • Comma-separated schema parsing for the plan command.
  • Multi-schema IR inspection and fingerprinting.
  • Schema create/drop diff output.
  • Deferred foreign key emission after structural changes.
  • Updated plan documentation and formatter grouping for schemas.

Confidence Score: 2/5

This should be fixed before merging.

  • Multi-schema planning can read or mutate real secondary schemas instead of isolated desired-state schemas.
  • External plan databases can keep secondary-schema objects after a plan run.
  • Schema owner changes are detected but omitted from the generated SQL.
  • Schema drops can remove extra ignored or unsupported objects through CASCADE.

cmd/plan/plan.go, internal/postgres/external.go, internal/postgres/embedded.go, internal/diff/schema.go, internal/diff/diff.go, testutil/postgres.go.

Important Files Changed

Filename Overview
cmd/plan/plan.go Coordinates multi-schema planning and desired-state inspection.
internal/postgres/external.go Applies desired SQL to external plan databases and cleans up temporary schema state.
internal/diff/schema.go Generates schema create/drop migration statements.
internal/diff/diff.go Detects schema additions, removals, and owner changes.
testutil/postgres.go Selects embedded PostgreSQL version for tests.

Comments Outside Diff (3)

  1. internal/postgres/external.go, line 169-176 (link)

    P1 Plan schemas leak state

    External plan cleanup only drops ed.tempSchema. In multi-schema mode, desired SQL can create or mutate secondary schemas such as app because only the primary schema qualification is rewritten before execution. Those secondary schemas are left behind after Stop, so later plans using the same external database can inspect stale objects or collide with objects from a previous run.

  2. internal/diff/diff.go, line 479-485 (link)

    P1 Schema owners are ignored

    This records owner drift in modifiedSchemas, but no later generation step emits an ALTER SCHEMA ... OWNER TO ... statement for those entries. If the current app schema is owned by old_owner and the desired IR has new_owner, the plan completes without any SQL to fix that difference.

  3. testutil/postgres.go, line 152-156 (link)

    P2 Version override is ignored

    The helper comment says tests use PGSCHEMA_POSTGRES_VERSION, but the implementation now always returns Postgres 15. Developers or CI setting PGSCHEMA_POSTGRES_VERSION=18 to exercise version-specific behavior still run against 15, so tests can fail for the wrong reason or miss coverage for newer PostgreSQL features.

Reviews (1): Last reviewed commit: "Add GetObjectName method for Schema type" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds multi-schema support to pgschema plan: --schema now accepts a comma-separated list, with the first entry treated as the primary (strip/normalize target) and the rest introspected on both target and plan databases. The diff engine gains a DiffTypeSchema and emits CREATE SCHEMA IF NOT EXISTS / DROP SCHEMA … CASCADE statements. Foreign keys produced during CREATE and ALTER TABLE flows are now uniformly deferred and flushed (topologically sorted) after the modify phase, so cross-table/cross-schema FK dependencies resolve correctly.

Changes:

  • New BuildIRFromSchemas / ComputeFingerprintForSchemas and ParseSchemaList, wired through cmd/plan to build a merged multi-schema IR for both sides of the diff.
  • New DiffTypeSchema with generateCreateSchemasSQL / generateDropSchemasSQL, plus formatter/grouping support.
  • All FK constraint emissions now go through collector.queueDeferredForeignKey + flushDeferredForeignKeys, with a new sortDeferredForeignKeys topological pass.

Reviewed changes

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

Show a summary per file
File Description
testutil/postgres.go Hard-codes PG version to 15 and comments out os import (debug leftover).
README.md Documents multi-schema plan usage.
docs/cli/plan.mdx Expanded --schema docs and multi-schema example.
cmd/plan/README.md Rewritten with single- vs multi-schema behavior and caveats.
cmd/plan/plan.go Parses schema list, picks primary, builds inspect spec, normalizes temp→primary.
cmd/util/connection.go Adds ParseSchemaList, switches to BuildIRFromSchemas.
ir/inspector.go Splits BuildIR into single/multi paths; adds buildSchemaContent and BuildIRFromSchemas.
ir/ir.go Adds Schema.GetObjectName.
internal/fingerprint/fingerprint.go Adds ComputeFingerprintForSchemas over a schema subset.
internal/diff/diff.go New DiffTypeSchema, emits schema create/drop, defers FK flush after modify phase.
internal/diff/schema.go (+test) New file generating CREATE/DROP SCHEMA DDL.
internal/diff/collector.go Pending FK queue and flush API on the collector.
internal/diff/table.go Routes FKs through deferred queue; refactors generateDeferredConstraintsSQL.
internal/diff/topological.go (+test) New sortDeferredForeignKeys topo sort for deferred FKs.
internal/dump/formatter.go Adds schema object directory and grouping for DiffTypeSchema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread testutil/postgres.go
Comment on lines 8 to +156
@@ -153,21 +153,7 @@ func ConnectToPostgres(t testing.TB, embeddedPG *postgres.EmbeddedPostgres) (con
// It reads from the PGSCHEMA_POSTGRES_VERSION environment variable,
// defaulting to "18" if not set.
func getPostgresVersion() postgres.PostgresVersion {
versionStr := os.Getenv("PGSCHEMA_POSTGRES_VERSION")
switch versionStr {
case "14":
return embeddedpostgres.V14
case "15":
return embeddedpostgres.V15
case "16":
return embeddedpostgres.V16
case "17":
return embeddedpostgres.V17
case "18", "":
return embeddedpostgres.V18
default:
return embeddedpostgres.V18
}
return embeddedpostgres.PostgresVersion("15")
Comment thread cmd/plan/plan.go
Comment on lines 316 to +331
@@ -319,7 +328,7 @@ func GeneratePlan(config *PlanConfig, provider postgres.DesiredStateProvider) (*
providerSSLMode = "prefer"
}
}
desiredStateIR, err := util.GetIRFromDatabase(providerHost, providerPort, providerDB, providerUsername, providerPassword, providerSSLMode, schemaToInspect, config.ApplicationName, ignoreConfig)
desiredStateIR, err := util.GetIRFromDatabase(providerHost, providerPort, providerDB, providerUsername, providerPassword, providerSSLMode, inspectSpec, config.ApplicationName, ignoreConfig)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Secondary schemas bypass isolation

The desired side now inspects the provider's temporary schema plus the remaining schema names from --schema, but ApplySchema only rewrites the primary schema. With --schema public,app and desired SQL such as CREATE TABLE app.widgets (...), embedded planning fails because only the temp schema was created. With an external plan database, app is read from the real validation database, so stale or unrelated objects in that schema can be included in the desired IR.

Comment thread internal/diff/schema.go
Comment on lines +44 to +57
func generateDropSchemasSQL(schemas []*ir.Schema, collector *diffCollector) {
if len(schemas) == 0 {
return
}
sorted := append([]*ir.Schema(nil), schemas...)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Name > sorted[j].Name
})
for _, s := range sorted {
if s == nil {
continue
}
sql := fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", ir.QuoteIdentifier(s.Name))
ctx := &diffContext{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Schema drops cascade broadly

Dropped schemas are always emitted with CASCADE. If a planned schema contains ignored objects or object types pgschema does not model, the generated migration can remove them even though they were not visible as individual drops in the plan. A multi-schema plan that omits app from the desired state can therefore delete extra objects in app, not just the objects represented in the IR.

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.

2 participants