@supabase/pg-delta 1.0.0-alpha.4 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -23
- package/dist/cli/app.js +26 -3
- package/dist/cli/bin/cli.js +5 -0
- package/dist/cli/commands/catalog-export.d.ts +5 -0
- package/dist/cli/commands/catalog-export.js +64 -0
- package/dist/cli/commands/declarative-apply.d.ts +6 -0
- package/dist/cli/commands/declarative-apply.js +288 -0
- package/dist/cli/commands/declarative-export.d.ts +5 -0
- package/dist/cli/commands/declarative-export.js +245 -0
- package/dist/cli/commands/plan.js +19 -6
- package/dist/cli/exit-code.d.ts +2 -0
- package/dist/cli/exit-code.js +7 -0
- package/dist/cli/formatters/tree/tree.js +3 -2
- package/dist/cli/utils/apply-display.d.ts +52 -0
- package/dist/cli/utils/apply-display.js +183 -0
- package/dist/cli/utils/export-display.d.ts +43 -0
- package/dist/cli/utils/export-display.js +202 -0
- package/dist/cli/utils/resolve-input.d.ts +7 -0
- package/dist/cli/utils/resolve-input.js +13 -0
- package/dist/core/catalog-export/index.d.ts +11 -0
- package/dist/core/catalog-export/index.js +10 -0
- package/dist/core/catalog.diff.d.ts +1 -0
- package/dist/core/catalog.diff.js +64 -48
- package/dist/core/catalog.model.d.ts +14 -1
- package/dist/core/catalog.model.js +103 -1
- package/dist/core/catalog.snapshot.d.ts +66 -0
- package/dist/core/catalog.snapshot.js +206 -0
- package/dist/core/declarative-apply/discover-sql.d.ts +18 -0
- package/dist/core/declarative-apply/discover-sql.js +86 -0
- package/dist/core/declarative-apply/extract-catalog-providers.d.ts +23 -0
- package/dist/core/declarative-apply/extract-catalog-providers.js +159 -0
- package/dist/core/declarative-apply/index.d.ts +49 -0
- package/dist/core/declarative-apply/index.js +134 -0
- package/dist/core/declarative-apply/round-apply.d.ts +100 -0
- package/dist/core/declarative-apply/round-apply.js +378 -0
- package/dist/core/export/file-mapper.d.ts +71 -0
- package/dist/core/export/file-mapper.js +474 -0
- package/dist/core/export/grouper.d.ts +13 -0
- package/dist/core/export/grouper.js +76 -0
- package/dist/core/export/index.d.ts +45 -0
- package/dist/core/export/index.js +63 -0
- package/dist/core/export/types.d.ts +84 -0
- package/dist/core/export/types.js +25 -0
- package/dist/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/dist/core/integrations/filter/dsl.d.ts +38 -1
- package/dist/core/integrations/filter/dsl.js +20 -2
- package/dist/core/integrations/filter/extractors.js +42 -0
- package/dist/core/integrations/integration-dsl.d.ts +10 -0
- package/dist/core/integrations/supabase.d.ts +8 -0
- package/dist/core/integrations/supabase.js +9 -0
- package/dist/core/objects/aggregate/aggregate.diff.d.ts +2 -8
- package/dist/core/objects/aggregate/aggregate.diff.js +16 -70
- package/dist/core/objects/aggregate/aggregate.model.d.ts +8 -8
- package/dist/core/objects/aggregate/aggregate.model.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.create.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.drop.js +1 -1
- package/dist/core/objects/base.privilege-diff.d.ts +38 -13
- package/dist/core/objects/base.privilege-diff.js +104 -22
- package/dist/core/objects/base.privilege.d.ts +1 -0
- package/dist/core/objects/base.privilege.js +9 -2
- package/dist/core/objects/collation/collation.diff.d.ts +2 -3
- package/dist/core/objects/diff-context.d.ts +15 -0
- package/dist/core/objects/diff-context.js +1 -0
- package/dist/core/objects/domain/changes/domain.create.js +4 -2
- package/dist/core/objects/domain/domain.diff.d.ts +2 -8
- package/dist/core/objects/domain/domain.diff.js +16 -77
- package/dist/core/objects/domain/domain.model.js +1 -1
- package/dist/core/objects/event-trigger/event-trigger.diff.d.ts +2 -3
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.js +13 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +2 -2
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +1 -1
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.js +13 -77
- package/dist/core/objects/language/language.diff.d.ts +2 -5
- package/dist/core/objects/language/language.diff.js +7 -39
- package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +2 -8
- package/dist/core/objects/materialized-view/materialized-view.diff.js +16 -158
- package/dist/core/objects/materialized-view/materialized-view.model.d.ts +3 -3
- package/dist/core/objects/materialized-view/materialized-view.model.js +1 -1
- package/dist/core/objects/procedure/changes/procedure.alter.js +12 -12
- package/dist/core/objects/procedure/procedure.diff.d.ts +2 -8
- package/dist/core/objects/procedure/procedure.diff.js +16 -77
- package/dist/core/objects/procedure/procedure.model.d.ts +9 -9
- package/dist/core/objects/procedure/procedure.model.js +1 -1
- package/dist/core/objects/publication/changes/publication.alter.d.ts +0 -9
- package/dist/core/objects/publication/changes/publication.alter.js +0 -14
- package/dist/core/objects/publication/changes/publication.types.d.ts +2 -2
- package/dist/core/objects/publication/publication.diff.d.ts +2 -3
- package/dist/core/objects/publication/publication.diff.js +8 -13
- package/dist/core/objects/rls-policy/changes/rls-policy.alter.js +3 -3
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
- package/dist/core/objects/role/role.diff.js +22 -1
- package/dist/core/objects/role/role.model.d.ts +4 -3
- package/dist/core/objects/role/role.model.js +118 -12
- package/dist/core/objects/rule/rule.model.d.ts +1 -1
- package/dist/core/objects/schema/schema.diff.d.ts +2 -8
- package/dist/core/objects/schema/schema.diff.js +16 -77
- package/dist/core/objects/schema/schema.model.js +1 -1
- package/dist/core/objects/sequence/sequence.diff.d.ts +2 -8
- package/dist/core/objects/sequence/sequence.diff.js +16 -79
- package/dist/core/objects/sequence/sequence.model.js +1 -1
- package/dist/core/objects/subscription/subscription.diff.d.ts +2 -3
- package/dist/core/objects/table/changes/table.create.js +3 -0
- package/dist/core/objects/table/table.diff.d.ts +2 -8
- package/dist/core/objects/table/table.diff.js +26 -157
- package/dist/core/objects/table/table.model.d.ts +23 -22
- package/dist/core/objects/table/table.model.js +1 -1
- package/dist/core/objects/trigger/changes/trigger.create.js +2 -4
- package/dist/core/objects/trigger/trigger.model.d.ts +8 -0
- package/dist/core/objects/trigger/trigger.model.js +11 -0
- package/dist/core/objects/type/composite-type/composite-type.diff.d.ts +2 -8
- package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -77
- package/dist/core/objects/type/composite-type/composite-type.model.d.ts +3 -3
- package/dist/core/objects/type/composite-type/composite-type.model.js +2 -1
- package/dist/core/objects/type/enum/enum.diff.d.ts +2 -8
- package/dist/core/objects/type/enum/enum.diff.js +25 -112
- package/dist/core/objects/type/enum/enum.model.js +1 -1
- package/dist/core/objects/type/range/changes/range.create.js +6 -3
- package/dist/core/objects/type/range/range.diff.d.ts +2 -8
- package/dist/core/objects/type/range/range.diff.js +16 -77
- package/dist/core/objects/type/range/range.model.js +1 -1
- package/dist/core/objects/view/view.diff.d.ts +2 -8
- package/dist/core/objects/view/view.diff.js +16 -158
- package/dist/core/objects/view/view.model.d.ts +18 -4
- package/dist/core/objects/view/view.model.js +3 -13
- package/dist/core/plan/apply.js +9 -26
- package/dist/core/plan/create.d.ts +19 -6
- package/dist/core/plan/create.js +134 -174
- package/dist/core/plan/serialize.js +16 -4
- package/dist/core/plan/sql-format/fixtures.js +3 -5
- package/dist/core/plan/sql-format/keyword-case.js +26 -1
- package/dist/core/plan/ssl-config.d.ts +32 -0
- package/dist/core/plan/ssl-config.js +115 -0
- package/dist/core/plan/types.d.ts +6 -0
- package/dist/core/postgres-config.d.ts +14 -0
- package/dist/core/postgres-config.js +53 -2
- package/dist/core/sort/graph-builder.js +10 -0
- package/dist/core/sort/logical-sort.js +31 -23
- package/dist/core/test-utils/assert-valid-sql.d.ts +10 -0
- package/dist/core/test-utils/assert-valid-sql.js +19 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -1
- package/package.json +21 -4
- package/src/cli/app.ts +27 -3
- package/src/cli/bin/cli.ts +6 -0
- package/src/cli/commands/catalog-export.ts +78 -0
- package/src/cli/commands/declarative-apply.diagnostics.test.ts +77 -0
- package/src/cli/commands/declarative-apply.ts +380 -0
- package/src/cli/commands/declarative-export.ts +330 -0
- package/src/cli/commands/plan.ts +28 -7
- package/src/cli/exit-code.test.ts +19 -0
- package/src/cli/exit-code.ts +7 -0
- package/src/cli/formatters/tree/tree.ts +3 -2
- package/src/cli/utils/apply-display.test.ts +348 -0
- package/src/cli/utils/apply-display.ts +238 -0
- package/src/cli/utils/export-display.test.ts +103 -0
- package/src/cli/utils/export-display.ts +275 -0
- package/src/cli/utils/integrations.test.ts +44 -0
- package/src/cli/utils/resolve-input.test.ts +38 -0
- package/src/cli/utils/resolve-input.ts +17 -0
- package/src/core/catalog-export/index.ts +20 -0
- package/src/core/catalog.diff.ts +79 -78
- package/src/core/catalog.model.test.ts +122 -0
- package/src/core/catalog.model.ts +127 -1
- package/src/core/catalog.snapshot.test.ts +464 -0
- package/src/core/catalog.snapshot.ts +289 -0
- package/src/core/declarative-apply/discover-sql.test.ts +103 -0
- package/src/core/declarative-apply/discover-sql.ts +107 -0
- package/src/core/declarative-apply/extract-catalog-providers.ts +220 -0
- package/src/core/declarative-apply/index.test.ts +67 -0
- package/src/core/declarative-apply/index.ts +205 -0
- package/src/core/declarative-apply/round-apply.test.ts +504 -0
- package/src/core/declarative-apply/round-apply.ts +562 -0
- package/src/core/expand-replace-dependencies.test.ts +70 -0
- package/src/core/export/file-mapper.test.ts +816 -0
- package/src/core/export/file-mapper.ts +574 -0
- package/src/core/export/grouper.ts +108 -0
- package/src/core/export/index.ts +129 -0
- package/src/core/export/types.ts +104 -0
- package/src/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/src/core/integrations/filter/dsl.test.ts +211 -0
- package/src/core/integrations/filter/dsl.ts +65 -3
- package/src/core/integrations/filter/extractors.test.ts +244 -0
- package/src/core/integrations/filter/extractors.ts +42 -0
- package/src/core/integrations/integration-dsl.ts +10 -0
- package/src/core/integrations/serialize/dsl.test.ts +91 -0
- package/src/core/integrations/supabase.ts +9 -0
- package/src/core/objects/aggregate/aggregate.diff.ts +39 -95
- package/src/core/objects/aggregate/aggregate.model.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.alter.test.ts +3 -1
- package/src/core/objects/aggregate/changes/aggregate.comment.test.ts +5 -2
- package/src/core/objects/aggregate/changes/aggregate.create.test.ts +6 -3
- package/src/core/objects/aggregate/changes/aggregate.create.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.drop.test.ts +7 -3
- package/src/core/objects/aggregate/changes/aggregate.drop.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +9 -3
- package/src/core/objects/base.privilege-diff.ts +178 -30
- package/src/core/objects/base.privilege.ts +9 -2
- package/src/core/objects/collation/changes/collation.alter.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.create.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.drop.test.ts +4 -1
- package/src/core/objects/collation/collation.diff.test.ts +9 -12
- package/src/core/objects/collation/collation.diff.ts +2 -1
- package/src/core/objects/diff-context.ts +16 -0
- package/src/core/objects/domain/changes/domain.alter.test.ts +28 -9
- package/src/core/objects/domain/changes/domain.create.test.ts +32 -2
- package/src/core/objects/domain/changes/domain.create.ts +7 -1
- package/src/core/objects/domain/changes/domain.drop.test.ts +4 -1
- package/src/core/objects/domain/domain.diff.ts +39 -102
- package/src/core/objects/domain/domain.model.ts +1 -1
- package/src/core/objects/event-trigger/changes/event-trigger.alter.test.ts +10 -3
- package/src/core/objects/event-trigger/changes/event-trigger.create.test.ts +4 -1
- package/src/core/objects/event-trigger/changes/event-trigger.drop.test.ts +4 -1
- package/src/core/objects/event-trigger/event-trigger.diff.test.ts +12 -7
- package/src/core/objects/event-trigger/event-trigger.diff.ts +2 -1
- package/src/core/objects/extension/changes/extension.alter.test.ts +7 -2
- package/src/core/objects/extension/changes/extension.create.test.ts +4 -1
- package/src/core/objects/extension/changes/extension.drop.test.ts +4 -1
- package/src/core/objects/extension/extension.model.test.ts +98 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +51 -16
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.test.ts +111 -4
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +2 -2
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +46 -15
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +39 -102
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +22 -7
- package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +19 -6
- package/src/core/objects/foreign-data-wrapper/server/changes/server.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/server/server.diff.test.ts +95 -0
- package/src/core/objects/foreign-data-wrapper/server/server.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.drop.test.ts +10 -3
- package/src/core/objects/index/changes/index.alter.test.ts +13 -4
- package/src/core/objects/index/changes/index.create.test.ts +4 -1
- package/src/core/objects/index/changes/index.drop.test.ts +4 -1
- package/src/core/objects/language/changes/language.alter.test.ts +4 -1
- package/src/core/objects/language/changes/language.create.test.ts +4 -1
- package/src/core/objects/language/changes/language.drop.test.ts +4 -1
- package/src/core/objects/language/language.diff.test.ts +86 -4
- package/src/core/objects/language/language.diff.ts +17 -49
- package/src/core/objects/materialized-view/changes/materialized-view.alter.test.ts +10 -3
- package/src/core/objects/materialized-view/changes/materialized-view.create.test.ts +7 -2
- package/src/core/objects/materialized-view/changes/materialized-view.drop.test.ts +4 -1
- package/src/core/objects/materialized-view/materialized-view.diff.test.ts +162 -0
- package/src/core/objects/materialized-view/materialized-view.diff.ts +41 -191
- package/src/core/objects/materialized-view/materialized-view.model.ts +1 -1
- package/src/core/objects/procedure/changes/procedure.alter.test.ts +121 -49
- package/src/core/objects/procedure/changes/procedure.alter.ts +15 -12
- package/src/core/objects/procedure/changes/procedure.create.test.ts +4 -1
- package/src/core/objects/procedure/changes/procedure.drop.test.ts +7 -2
- package/src/core/objects/procedure/procedure.diff.ts +39 -102
- package/src/core/objects/procedure/procedure.model.ts +1 -1
- package/src/core/objects/publication/changes/publication.alter.test.ts +15 -21
- package/src/core/objects/publication/changes/publication.alter.ts +0 -18
- package/src/core/objects/publication/changes/publication.comment.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.create.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.drop.test.ts +3 -1
- package/src/core/objects/publication/changes/publication.types.ts +0 -2
- package/src/core/objects/publication/publication.diff.test.ts +24 -19
- package/src/core/objects/publication/publication.diff.ts +9 -15
- package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +31 -14
- package/src/core/objects/rls-policy/changes/rls-policy.alter.ts +3 -3
- package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +10 -3
- package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +4 -1
- package/src/core/objects/role/changes/role.alter.test.ts +31 -15
- package/src/core/objects/role/changes/role.create.test.ts +6 -2
- package/src/core/objects/role/changes/role.drop.test.ts +4 -1
- package/src/core/objects/role/role.diff.test.ts +235 -0
- package/src/core/objects/role/role.diff.ts +21 -1
- package/src/core/objects/role/role.model.ts +122 -14
- package/src/core/objects/rule/changes/rule.alter.test.ts +7 -3
- package/src/core/objects/rule/changes/rule.comment.test.ts +5 -2
- package/src/core/objects/rule/changes/rule.create.test.ts +6 -2
- package/src/core/objects/rule/changes/rule.drop.test.ts +3 -1
- package/src/core/objects/schema/changes/schema.alter.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.create.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.drop.test.ts +4 -1
- package/src/core/objects/schema/schema.diff.ts +39 -102
- package/src/core/objects/schema/schema.model.ts +1 -1
- package/src/core/objects/sequence/changes/sequence.alter.test.ts +11 -5
- package/src/core/objects/sequence/changes/sequence.create.test.ts +8 -3
- package/src/core/objects/sequence/changes/sequence.drop.test.ts +4 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +114 -0
- package/src/core/objects/sequence/sequence.diff.ts +39 -104
- package/src/core/objects/sequence/sequence.model.ts +1 -1
- package/src/core/objects/subscription/changes/subscription.alter.test.ts +15 -5
- package/src/core/objects/subscription/changes/subscription.comment.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.create.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.drop.test.ts +3 -1
- package/src/core/objects/subscription/subscription.diff.test.ts +16 -11
- package/src/core/objects/subscription/subscription.diff.ts +2 -1
- package/src/core/objects/table/changes/table.alter.test.ts +38 -15
- package/src/core/objects/table/changes/table.create.test.ts +41 -3
- package/src/core/objects/table/changes/table.create.ts +4 -0
- package/src/core/objects/table/changes/table.drop.test.ts +3 -1
- package/src/core/objects/table/table.diff.test.ts +157 -0
- package/src/core/objects/table/table.diff.ts +54 -190
- package/src/core/objects/table/table.model.ts +1 -1
- package/src/core/objects/trigger/changes/trigger.alter.test.ts +8 -4
- package/src/core/objects/trigger/changes/trigger.create.test.ts +5 -1
- package/src/core/objects/trigger/changes/trigger.create.ts +7 -4
- package/src/core/objects/trigger/changes/trigger.drop.test.ts +5 -1
- package/src/core/objects/trigger/trigger.diff.test.ts +1 -0
- package/src/core/objects/trigger/trigger.model.ts +12 -0
- package/src/core/objects/type/composite-type/changes/composite-type.alter.test.ts +10 -4
- package/src/core/objects/type/composite-type/changes/composite-type.create.test.ts +7 -2
- package/src/core/objects/type/composite-type/changes/composite-type.drop.test.ts +4 -1
- package/src/core/objects/type/composite-type/composite-type.diff.test.ts +78 -0
- package/src/core/objects/type/composite-type/composite-type.diff.ts +39 -101
- package/src/core/objects/type/composite-type/composite-type.model.ts +2 -1
- package/src/core/objects/type/enum/changes/enum.alter.test.ts +14 -5
- package/src/core/objects/type/enum/changes/enum.create.test.ts +4 -1
- package/src/core/objects/type/enum/changes/enum.drop.test.ts +4 -1
- package/src/core/objects/type/enum/enum.diff.test.ts +181 -0
- package/src/core/objects/type/enum/enum.diff.ts +58 -146
- package/src/core/objects/type/enum/enum.model.ts +1 -1
- package/src/core/objects/type/range/changes/range.alter.test.ts +3 -1
- package/src/core/objects/type/range/changes/range.create.test.ts +5 -2
- package/src/core/objects/type/range/changes/range.create.ts +6 -2
- package/src/core/objects/type/range/changes/range.drop.test.ts +3 -1
- package/src/core/objects/type/range/range.diff.test.ts +77 -0
- package/src/core/objects/type/range/range.diff.ts +39 -101
- package/src/core/objects/type/range/range.model.ts +1 -1
- package/src/core/objects/view/changes/view.alter.test.ts +8 -3
- package/src/core/objects/view/changes/view.create.test.ts +7 -2
- package/src/core/objects/view/changes/view.drop.test.ts +4 -1
- package/src/core/objects/view/view.diff.test.ts +82 -0
- package/src/core/objects/view/view.diff.ts +41 -191
- package/src/core/objects/view/view.model.ts +3 -17
- package/src/core/plan/apply.ts +9 -27
- package/src/core/plan/create.ts +173 -237
- package/src/core/plan/serialize.test.ts +317 -0
- package/src/core/plan/serialize.ts +18 -4
- package/src/core/plan/sql-format/fixtures.ts +2 -5
- package/src/core/plan/sql-format/format-lowercase-coverage.test.ts +52 -0
- package/src/core/plan/sql-format/format-off.test.ts +14 -17
- package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +27 -22
- package/src/core/plan/sql-format/format-pretty-narrow.test.ts +17 -21
- package/src/core/plan/sql-format/format-pretty-preserve.test.ts +25 -20
- package/src/core/plan/sql-format/format-pretty-upper.test.ts +23 -20
- package/src/core/plan/sql-format/keyword-case.ts +36 -1
- package/src/core/plan/ssl-config.ts +172 -0
- package/src/core/plan/types.ts +6 -0
- package/src/core/postgres-config.ts +71 -2
- package/src/core/sort/graph-builder.ts +12 -0
- package/src/core/sort/logical-sort.test.ts +371 -0
- package/src/core/sort/logical-sort.ts +32 -25
- package/src/core/sort/topological-sort.test.ts +275 -0
- package/src/core/test-utils/assert-valid-sql.ts +20 -0
- package/src/index.ts +26 -2
|
@@ -4,6 +4,7 @@ import z from "zod";
|
|
|
4
4
|
import {
|
|
5
5
|
BasePgModel,
|
|
6
6
|
columnPropsSchema,
|
|
7
|
+
normalizeColumns,
|
|
7
8
|
type TableLikeObject,
|
|
8
9
|
} from "../base.model.ts";
|
|
9
10
|
import {
|
|
@@ -115,26 +116,11 @@ export class View extends BasePgModel implements TableLikeObject {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
override stableSnapshot() {
|
|
118
|
-
const normalizeColumns = () =>
|
|
119
|
-
[...this.columns]
|
|
120
|
-
.map((col) => {
|
|
121
|
-
const { position: _pos, ...rest } = col as unknown as Record<
|
|
122
|
-
string,
|
|
123
|
-
unknown
|
|
124
|
-
>;
|
|
125
|
-
return rest;
|
|
126
|
-
})
|
|
127
|
-
.sort((a, b) => {
|
|
128
|
-
const nameA = (a.name as string | undefined) ?? "";
|
|
129
|
-
const nameB = (b.name as string | undefined) ?? "";
|
|
130
|
-
return nameA.localeCompare(nameB);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
119
|
return {
|
|
134
120
|
identity: this.identityFields,
|
|
135
121
|
data: {
|
|
136
122
|
...this.dataFields,
|
|
137
|
-
columns: normalizeColumns(),
|
|
123
|
+
columns: normalizeColumns(this.columns),
|
|
138
124
|
},
|
|
139
125
|
};
|
|
140
126
|
}
|
|
@@ -244,7 +230,7 @@ select
|
|
|
244
230
|
from (
|
|
245
231
|
-- one row for object ACL + one row per column ACL
|
|
246
232
|
select null::name as attname, v.oid as relacl_oid, (
|
|
247
|
-
select c_rel.relacl from pg_class c_rel where c_rel.oid = v.oid
|
|
233
|
+
select COALESCE(c_rel.relacl, acldefault('r', c_rel.relowner)) from pg_class c_rel where c_rel.oid = v.oid
|
|
248
234
|
) as acl
|
|
249
235
|
union all
|
|
250
236
|
select a2.attname, v.oid as relacl_oid, a2.attacl
|
package/src/core/plan/apply.ts
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { Pool } from "pg";
|
|
6
|
-
import { escapeIdentifier } from "pg";
|
|
7
6
|
import { diffCatalogs } from "../catalog.diff.ts";
|
|
8
7
|
import { extractCatalog } from "../catalog.model.ts";
|
|
9
8
|
import type { DiffContext } from "../context.ts";
|
|
10
9
|
import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.ts";
|
|
11
10
|
import { compileFilterDSL } from "../integrations/filter/dsl.ts";
|
|
12
|
-
import {
|
|
11
|
+
import { createManagedPool, endPool } from "../postgres-config.ts";
|
|
13
12
|
import { sortChanges } from "../sort/sort-changes.ts";
|
|
14
13
|
import type { Plan } from "./types.ts";
|
|
15
14
|
|
|
@@ -57,40 +56,23 @@ export async function applyPlan(
|
|
|
57
56
|
let shouldCloseCurrent = false;
|
|
58
57
|
let shouldCloseDesired = false;
|
|
59
58
|
|
|
60
|
-
// Suppress expected shutdown errors from idle pool connections (57P01 = admin_shutdown)
|
|
61
|
-
const onError = (err: Error & { code?: string }) => {
|
|
62
|
-
if (err.code !== "57P01") {
|
|
63
|
-
console.error("Pool error:", err);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
59
|
if (typeof source === "string") {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Force fully qualified names in catalog queries for fingerprint verification
|
|
72
|
-
await client.query("SET search_path = ''");
|
|
73
|
-
if (plan.role) {
|
|
74
|
-
await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
|
|
75
|
-
}
|
|
76
|
-
},
|
|
60
|
+
const managed = await createManagedPool(source, {
|
|
61
|
+
role: plan.role,
|
|
62
|
+
label: "source",
|
|
77
63
|
});
|
|
64
|
+
currentPool = managed.pool;
|
|
78
65
|
shouldCloseCurrent = true;
|
|
79
66
|
} else {
|
|
80
67
|
currentPool = source;
|
|
81
68
|
}
|
|
82
69
|
|
|
83
70
|
if (typeof target === "string") {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Force fully qualified names in catalog queries for fingerprint verification
|
|
88
|
-
await client.query("SET search_path = ''");
|
|
89
|
-
if (plan.role) {
|
|
90
|
-
await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
71
|
+
const managed = await createManagedPool(target, {
|
|
72
|
+
role: plan.role,
|
|
73
|
+
label: "target",
|
|
93
74
|
});
|
|
75
|
+
desiredPool = managed.pool;
|
|
94
76
|
shouldCloseDesired = true;
|
|
95
77
|
} else {
|
|
96
78
|
desiredPool = target;
|
package/src/core/plan/create.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Plan creation - the main entry point for creating migration plans.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
5
|
import type { Pool } from "pg";
|
|
7
6
|
import { escapeIdentifier } from "pg";
|
|
8
7
|
import { diffCatalogs } from "../catalog.diff.ts";
|
|
9
|
-
import
|
|
10
|
-
|
|
8
|
+
import {
|
|
9
|
+
Catalog,
|
|
10
|
+
createEmptyCatalog,
|
|
11
|
+
extractCatalog,
|
|
12
|
+
} from "../catalog.model.ts";
|
|
11
13
|
import type { Change } from "../change.types.ts";
|
|
12
14
|
import type { DiffContext } from "../context.ts";
|
|
13
15
|
import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.ts";
|
|
@@ -20,8 +22,9 @@ import {
|
|
|
20
22
|
compileSerializeDSL,
|
|
21
23
|
type SerializeDSL,
|
|
22
24
|
} from "../integrations/serialize/dsl.ts";
|
|
23
|
-
import {
|
|
25
|
+
import { createManagedPool, endPool } from "../postgres-config.ts";
|
|
24
26
|
import { sortChanges } from "../sort/sort-changes.ts";
|
|
27
|
+
import type { PgDependRow } from "../sort/types.ts";
|
|
25
28
|
import { classifyChangesRisk } from "./risk.ts";
|
|
26
29
|
import type { CreatePlanOptions, Plan } from "./types.ts";
|
|
27
30
|
|
|
@@ -30,254 +33,79 @@ import type { CreatePlanOptions, Plan } from "./types.ts";
|
|
|
30
33
|
// ============================================================================
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param fromUrl - Source database connection URL (current state)
|
|
36
|
-
* @param toUrl - Target database connection URL (desired state)
|
|
37
|
-
* @param options - Optional configuration
|
|
38
|
-
* @returns A Plan if there are changes, null if databases are identical
|
|
36
|
+
* Input for source/target: a postgres connection URL, an existing Pool, or
|
|
37
|
+
* an already-resolved Catalog (e.g. deserialized from a snapshot file).
|
|
39
38
|
*/
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
type SslConfig = {
|
|
43
|
-
ssl?:
|
|
44
|
-
| boolean
|
|
45
|
-
| {
|
|
46
|
-
rejectUnauthorized: boolean;
|
|
47
|
-
ca?: string;
|
|
48
|
-
cert?: string;
|
|
49
|
-
key?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Custom server identity check function.
|
|
52
|
-
* Used to skip hostname verification for verify-ca mode.
|
|
53
|
-
* Returns undefined to indicate success (no error).
|
|
54
|
-
*/
|
|
55
|
-
checkServerIdentity?: () => undefined;
|
|
56
|
-
};
|
|
57
|
-
cleanedUrl: string;
|
|
58
|
-
};
|
|
39
|
+
export type CatalogInput = string | Pool | Catalog;
|
|
59
40
|
|
|
60
41
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* -
|
|
65
|
-
* -
|
|
66
|
-
*
|
|
42
|
+
* Create a migration plan by comparing two catalog states.
|
|
43
|
+
*
|
|
44
|
+
* Each input can be:
|
|
45
|
+
* - A postgres connection URL (string) -- a pool is created and catalog extracted
|
|
46
|
+
* - An existing pg Pool -- catalog is extracted directly
|
|
47
|
+
* - A Catalog instance -- used as-is (e.g. from a deserialized snapshot)
|
|
48
|
+
*
|
|
49
|
+
* When `source` is `null`, a minimal empty catalog (`createEmptyCatalog`) is
|
|
50
|
+
* used as the baseline. For a more accurate baseline, pass a Catalog
|
|
51
|
+
* deserialized from a snapshot of `template1` or another reference database.
|
|
52
|
+
*
|
|
53
|
+
* @param source - Source catalog input (current state), or null for empty baseline
|
|
54
|
+
* @param target - Target catalog input (desired state)
|
|
55
|
+
* @param options - Optional configuration
|
|
56
|
+
* @returns A Plan if there are changes, null if databases are identical
|
|
67
57
|
*/
|
|
68
|
-
async function parseSslConfig(
|
|
69
|
-
url: string,
|
|
70
|
-
connectionType: "source" | "target",
|
|
71
|
-
): Promise<SslConfig> {
|
|
72
|
-
const urlObj = new URL(url);
|
|
73
|
-
const sslmode = urlObj.searchParams.get("sslmode");
|
|
74
|
-
const sslrootcert = urlObj.searchParams.get("sslrootcert");
|
|
75
|
-
const sslcert = urlObj.searchParams.get("sslcert");
|
|
76
|
-
const sslkey = urlObj.searchParams.get("sslkey");
|
|
77
|
-
|
|
78
|
-
// Remove SSL-related query parameters since we parse them ourselves
|
|
79
|
-
urlObj.searchParams.delete("sslmode");
|
|
80
|
-
urlObj.searchParams.delete("sslrootcert");
|
|
81
|
-
urlObj.searchParams.delete("sslcert");
|
|
82
|
-
urlObj.searchParams.delete("sslkey");
|
|
83
|
-
const cleanedUrl = urlObj.toString();
|
|
84
|
-
|
|
85
|
-
// Handle different SSL modes
|
|
86
|
-
if (sslmode === "disable") {
|
|
87
|
-
return { cleanedUrl, ssl: false };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
sslmode === "require" ||
|
|
92
|
-
sslmode === "prefer" ||
|
|
93
|
-
sslmode === "verify-ca" ||
|
|
94
|
-
sslmode === "verify-full"
|
|
95
|
-
) {
|
|
96
|
-
// Helper function to get certificate value: query param (file path) takes precedence over env var (content)
|
|
97
|
-
const getCertValue = async (
|
|
98
|
-
queryParam: string | null,
|
|
99
|
-
envVarName: string,
|
|
100
|
-
): Promise<string | undefined> => {
|
|
101
|
-
// Prefer query parameter (file path)
|
|
102
|
-
if (queryParam) {
|
|
103
|
-
try {
|
|
104
|
-
return await readFile(queryParam, "utf-8");
|
|
105
|
-
} catch (error) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Failed to read certificate file '${queryParam}': ${error instanceof Error ? error.message : String(error)}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// Fallback to environment variable (content)
|
|
112
|
-
const envValue = process.env[envVarName];
|
|
113
|
-
return envValue || undefined;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const hasExplicitVerification =
|
|
117
|
-
sslmode === "verify-ca" || sslmode === "verify-full";
|
|
118
|
-
|
|
119
|
-
// Get CA certificate value.
|
|
120
|
-
// - verify-ca/verify-full: check query param first, then env var
|
|
121
|
-
// - require/prefer: only check query param (libpq backward compatibility
|
|
122
|
-
// requires an explicit root CA *file*, not a global env var)
|
|
123
|
-
const caEnvVar =
|
|
124
|
-
connectionType === "source"
|
|
125
|
-
? "PGDELTA_SOURCE_SSLROOTCERT"
|
|
126
|
-
: "PGDELTA_TARGET_SSLROOTCERT";
|
|
127
|
-
let caValue: string | undefined;
|
|
128
|
-
if (sslrootcert) {
|
|
129
|
-
// Explicit file path in query param — always honour it
|
|
130
|
-
caValue = await getCertValue(sslrootcert, caEnvVar);
|
|
131
|
-
} else if (hasExplicitVerification) {
|
|
132
|
-
// verify-ca / verify-full without file path — fall back to env var
|
|
133
|
-
caValue = await getCertValue(null, caEnvVar);
|
|
134
|
-
}
|
|
135
|
-
// require/prefer without sslrootcert: no CA cert, no verification
|
|
136
|
-
|
|
137
|
-
// Determine if we should verify the CA chain
|
|
138
|
-
// From PostgreSQL docs: "if a root CA file exists, the behavior of sslmode=require
|
|
139
|
-
// will be the same as that of verify-ca"
|
|
140
|
-
const hasLibpqCompatibility =
|
|
141
|
-
(sslmode === "require" || sslmode === "prefer") && caValue !== undefined;
|
|
142
|
-
const shouldVerifyCa = hasExplicitVerification || hasLibpqCompatibility;
|
|
143
|
-
|
|
144
|
-
// Determine if we should verify hostname
|
|
145
|
-
// - verify-full: verify both CA and hostname
|
|
146
|
-
// - verify-ca: verify CA only (skip hostname)
|
|
147
|
-
// - require/prefer with CA (libpq compat): behaves like verify-ca (skip hostname)
|
|
148
|
-
const shouldVerifyHostname = sslmode === "verify-full";
|
|
149
|
-
|
|
150
|
-
const ssl: {
|
|
151
|
-
rejectUnauthorized: boolean;
|
|
152
|
-
ca?: string;
|
|
153
|
-
cert?: string;
|
|
154
|
-
key?: string;
|
|
155
|
-
checkServerIdentity?: () => undefined;
|
|
156
|
-
} = {
|
|
157
|
-
rejectUnauthorized: shouldVerifyCa,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Add CA certificate if verifying
|
|
161
|
-
if (shouldVerifyCa && caValue) {
|
|
162
|
-
ssl.ca = caValue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// For verify-ca and libpq compatibility mode: skip hostname verification
|
|
166
|
-
// This matches PostgreSQL semantics where verify-ca only checks the CA chain
|
|
167
|
-
if (shouldVerifyCa && !shouldVerifyHostname) {
|
|
168
|
-
ssl.checkServerIdentity = () => undefined;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Get client certificate (optional, for mutual TLS)
|
|
172
|
-
const certEnvVar =
|
|
173
|
-
connectionType === "source"
|
|
174
|
-
? "PGDELTA_SOURCE_SSLCERT"
|
|
175
|
-
: "PGDELTA_TARGET_SSLCERT";
|
|
176
|
-
const certValue = await getCertValue(sslcert, certEnvVar);
|
|
177
|
-
if (certValue) {
|
|
178
|
-
ssl.cert = certValue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Get client key (optional, for mutual TLS, required if cert is provided)
|
|
182
|
-
const keyEnvVar =
|
|
183
|
-
connectionType === "source"
|
|
184
|
-
? "PGDELTA_SOURCE_SSLKEY"
|
|
185
|
-
: "PGDELTA_TARGET_SSLKEY";
|
|
186
|
-
const keyValue = await getCertValue(sslkey, keyEnvVar);
|
|
187
|
-
if (keyValue) {
|
|
188
|
-
ssl.key = keyValue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Warn if cert is provided without key (or vice versa)
|
|
192
|
-
if ((ssl.cert && !ssl.key) || (!ssl.cert && ssl.key)) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
"Both client certificate and key must be provided together for mutual TLS",
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { ssl, cleanedUrl };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// No sslmode specified or invalid value - explicitly disable SSL
|
|
202
|
-
return { cleanedUrl, ssl: false };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
58
|
export async function createPlan(
|
|
206
|
-
source:
|
|
207
|
-
target:
|
|
59
|
+
source: CatalogInput | null,
|
|
60
|
+
target: CatalogInput,
|
|
208
61
|
options: CreatePlanOptions = {},
|
|
209
62
|
): Promise<{ plan: Plan; sortedChanges: Change[]; ctx: DiffContext } | null> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
63
|
+
const resolvePool = async (
|
|
64
|
+
input: string | Pool,
|
|
65
|
+
label: "source" | "target",
|
|
66
|
+
): Promise<{ pool: Pool; shouldClose: boolean }> => {
|
|
67
|
+
if (typeof input === "string") {
|
|
68
|
+
const managed = await createManagedPool(input, {
|
|
69
|
+
role: options.role,
|
|
70
|
+
label,
|
|
71
|
+
});
|
|
72
|
+
return { pool: managed.pool, shouldClose: true };
|
|
219
73
|
}
|
|
74
|
+
return { pool: input, shouldClose: false };
|
|
220
75
|
};
|
|
221
76
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
sourcePool = source;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (typeof target === "string") {
|
|
241
|
-
const sslConfig = await parseSslConfig(target, "target");
|
|
242
|
-
targetPool = createPool(sslConfig.cleanedUrl, {
|
|
243
|
-
...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
|
|
244
|
-
onError,
|
|
245
|
-
onConnect: async (client) => {
|
|
246
|
-
// Force fully qualified names in catalog queries
|
|
247
|
-
await client.query("SET search_path = ''");
|
|
248
|
-
if (options.role) {
|
|
249
|
-
await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
shouldCloseTarget = true;
|
|
254
|
-
} else {
|
|
255
|
-
targetPool = target;
|
|
256
|
-
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a CatalogInput to a Catalog, tracking pools that need cleanup.
|
|
79
|
+
*/
|
|
80
|
+
const resolveCatalog = async (
|
|
81
|
+
input: CatalogInput,
|
|
82
|
+
label: "source" | "target",
|
|
83
|
+
pools: Array<{ pool: Pool; shouldClose: boolean }>,
|
|
84
|
+
): Promise<Catalog> => {
|
|
85
|
+
if (input instanceof Catalog) {
|
|
86
|
+
return input;
|
|
87
|
+
}
|
|
88
|
+
const resolved = await resolvePool(input, label);
|
|
89
|
+
pools.push(resolved);
|
|
90
|
+
return extractCatalog(resolved.pool);
|
|
91
|
+
};
|
|
257
92
|
|
|
258
|
-
const
|
|
259
|
-
const targetExtraction = extractCatalog(targetPool);
|
|
93
|
+
const pools: Array<{ pool: Pool; shouldClose: boolean }> = [];
|
|
260
94
|
|
|
261
95
|
try {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
96
|
+
const toCatalog = await resolveCatalog(target, "target", pools);
|
|
97
|
+
|
|
98
|
+
const fromCatalog =
|
|
99
|
+
source !== null
|
|
100
|
+
? await resolveCatalog(source, "source", pools)
|
|
101
|
+
: await createEmptyCatalog(toCatalog.version, toCatalog.currentUser);
|
|
266
102
|
|
|
267
103
|
return buildPlanForCatalogs(fromCatalog, toCatalog, options);
|
|
268
|
-
} catch (error) {
|
|
269
|
-
// When one extraction fails, the other may still have in-flight queries.
|
|
270
|
-
// Wait for both to settle before pool cleanup to prevent unhandled
|
|
271
|
-
// rejections from connections being terminated mid-flight.
|
|
272
|
-
await Promise.allSettled([sourceExtraction, targetExtraction]);
|
|
273
|
-
throw error;
|
|
274
104
|
} finally {
|
|
275
|
-
const closers
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (closers.length)
|
|
279
|
-
await Promise.all(closers);
|
|
280
|
-
}
|
|
105
|
+
const closers = pools
|
|
106
|
+
.filter((p) => p.shouldClose)
|
|
107
|
+
.map((p) => endPool(p.pool));
|
|
108
|
+
if (closers.length) await Promise.all(closers);
|
|
281
109
|
}
|
|
282
110
|
}
|
|
283
111
|
|
|
@@ -291,6 +119,7 @@ function buildPlanForCatalogs(
|
|
|
291
119
|
): { plan: Plan; sortedChanges: Change[]; ctx: DiffContext } | null {
|
|
292
120
|
const changes = diffCatalogs(fromCatalog, toCatalog, {
|
|
293
121
|
role: options.role,
|
|
122
|
+
skipDefaultPrivilegeSubtraction: options.skipDefaultPrivilegeSubtraction,
|
|
294
123
|
});
|
|
295
124
|
|
|
296
125
|
const filterOption = options.filter;
|
|
@@ -331,10 +160,24 @@ function buildPlanForCatalogs(
|
|
|
331
160
|
// Use filter from final integration
|
|
332
161
|
const filterFn = finalIntegration?.filter;
|
|
333
162
|
|
|
334
|
-
|
|
163
|
+
let filteredChanges = filterFn
|
|
335
164
|
? changes.filter((change) => filterFn(change))
|
|
336
165
|
: changes;
|
|
337
166
|
|
|
167
|
+
// Cascade dependency exclusions: when a change is excluded by the filter,
|
|
168
|
+
// also exclude changes that depend on it (via requires or pg_depend).
|
|
169
|
+
// DSL filters: cascade only if explicitly opted in (cascade: true). Function filters: cascade by default.
|
|
170
|
+
const shouldCascade = isFilterDSL
|
|
171
|
+
? (filterDSL as Record<string, unknown>)?.cascade === true
|
|
172
|
+
: true;
|
|
173
|
+
if (filterFn && filteredChanges.length < changes.length && shouldCascade) {
|
|
174
|
+
filteredChanges = cascadeExclusions(
|
|
175
|
+
filteredChanges,
|
|
176
|
+
changes,
|
|
177
|
+
toCatalog.depends,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
338
181
|
if (filteredChanges.length === 0) {
|
|
339
182
|
return null;
|
|
340
183
|
}
|
|
@@ -352,6 +195,99 @@ function buildPlanForCatalogs(
|
|
|
352
195
|
return { plan, sortedChanges, ctx };
|
|
353
196
|
}
|
|
354
197
|
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Dependency Cascading
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Cascade exclusions through dependency relationships.
|
|
204
|
+
*
|
|
205
|
+
* When a change is excluded by the filter, any change that depends on it
|
|
206
|
+
* (via explicit `requires` or via catalog `pg_depend`) should also be excluded.
|
|
207
|
+
* This runs as a fixpoint loop, bounded by the total number of changes to
|
|
208
|
+
* guarantee deterministic termination.
|
|
209
|
+
*
|
|
210
|
+
* @param filteredChanges - Changes that passed the initial filter
|
|
211
|
+
* @param allChanges - All changes before filtering
|
|
212
|
+
* @param catalogDepends - Dependency rows from the target catalog (pg_depend)
|
|
213
|
+
* @returns The filtered changes with cascading exclusions applied
|
|
214
|
+
*/
|
|
215
|
+
function cascadeExclusions(
|
|
216
|
+
filteredChanges: Change[],
|
|
217
|
+
allChanges: Change[],
|
|
218
|
+
catalogDepends: PgDependRow[],
|
|
219
|
+
): Change[] {
|
|
220
|
+
// Collect stableIds created by initially-excluded changes
|
|
221
|
+
const filteredSet = new Set(filteredChanges);
|
|
222
|
+
const excludedIds = new Set<string>();
|
|
223
|
+
for (const change of allChanges) {
|
|
224
|
+
if (!filteredSet.has(change)) {
|
|
225
|
+
for (const id of change.creates ?? []) {
|
|
226
|
+
excludedIds.add(id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (excludedIds.size === 0) {
|
|
232
|
+
return filteredChanges;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Build reverse dependency map: referenced_stable_id -> Set(dependent_stable_ids)
|
|
236
|
+
const catalogDependents = new Map<string, Set<string>>();
|
|
237
|
+
for (const dep of catalogDepends) {
|
|
238
|
+
const existing = catalogDependents.get(dep.referenced_stable_id);
|
|
239
|
+
if (existing) {
|
|
240
|
+
existing.add(dep.dependent_stable_id);
|
|
241
|
+
} else {
|
|
242
|
+
catalogDependents.set(
|
|
243
|
+
dep.referenced_stable_id,
|
|
244
|
+
new Set([dep.dependent_stable_id]),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Fixpoint loop: bounded by total changes to guarantee termination.
|
|
250
|
+
// Each iteration must remove at least one change, otherwise we break.
|
|
251
|
+
let result = filteredChanges;
|
|
252
|
+
for (let i = 0; i < allChanges.length; i++) {
|
|
253
|
+
const beforeLength = result.length;
|
|
254
|
+
result = result.filter((change) => {
|
|
255
|
+
// Check explicit requirements: does this change require an excluded id?
|
|
256
|
+
const requires = change.requires ?? [];
|
|
257
|
+
if (requires.some((dep) => excludedIds.has(dep))) {
|
|
258
|
+
for (const id of change.creates ?? []) {
|
|
259
|
+
excludedIds.add(id);
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check catalog dependencies: does anything this change creates
|
|
265
|
+
// depend on an excluded id via pg_depend?
|
|
266
|
+
const creates = change.creates ?? [];
|
|
267
|
+
for (const createdId of creates) {
|
|
268
|
+
for (const excludedId of excludedIds) {
|
|
269
|
+
const dependents = catalogDependents.get(excludedId);
|
|
270
|
+
if (dependents?.has(createdId)) {
|
|
271
|
+
for (const id of creates) {
|
|
272
|
+
excludedIds.add(id);
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return true;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// No changes removed this iteration — fixpoint reached
|
|
283
|
+
if (result.length === beforeLength) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
|
|
355
291
|
// ============================================================================
|
|
356
292
|
// Plan Building
|
|
357
293
|
// ============================================================================
|