@supabase/pg-delta 1.0.0-alpha.4 → 1.0.0-alpha.5
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
|
@@ -14,10 +14,10 @@ declare const viewPropsSchema: z.ZodObject<{
|
|
|
14
14
|
has_subclasses: z.ZodBoolean;
|
|
15
15
|
is_populated: z.ZodBoolean;
|
|
16
16
|
replica_identity: z.ZodEnum<{
|
|
17
|
-
f: "f";
|
|
18
17
|
n: "n";
|
|
19
18
|
i: "i";
|
|
20
19
|
d: "d";
|
|
20
|
+
f: "f";
|
|
21
21
|
}>;
|
|
22
22
|
is_partition: z.ZodBoolean;
|
|
23
23
|
options: z.ZodNullable<z.ZodArray<z.ZodString>>;
|
|
@@ -85,7 +85,7 @@ export declare class View extends BasePgModel implements TableLikeObject {
|
|
|
85
85
|
has_triggers: boolean;
|
|
86
86
|
has_subclasses: boolean;
|
|
87
87
|
is_populated: boolean;
|
|
88
|
-
replica_identity: "
|
|
88
|
+
replica_identity: "n" | "i" | "d" | "f";
|
|
89
89
|
is_partition: boolean;
|
|
90
90
|
options: string[] | null;
|
|
91
91
|
partition_bound: string | null;
|
|
@@ -123,7 +123,21 @@ export declare class View extends BasePgModel implements TableLikeObject {
|
|
|
123
123
|
};
|
|
124
124
|
data: {
|
|
125
125
|
columns: {
|
|
126
|
-
|
|
126
|
+
name: string;
|
|
127
|
+
data_type: string;
|
|
128
|
+
data_type_str: string;
|
|
129
|
+
is_custom_type: boolean;
|
|
130
|
+
custom_type_type: string | null;
|
|
131
|
+
custom_type_category: string | null;
|
|
132
|
+
custom_type_schema: string | null;
|
|
133
|
+
custom_type_name: string | null;
|
|
134
|
+
not_null: boolean;
|
|
135
|
+
is_identity: boolean;
|
|
136
|
+
is_identity_always: boolean;
|
|
137
|
+
is_generated: boolean;
|
|
138
|
+
collation: string | null;
|
|
139
|
+
default: string | null;
|
|
140
|
+
comment: string | null;
|
|
127
141
|
}[];
|
|
128
142
|
definition: string;
|
|
129
143
|
row_security: boolean;
|
|
@@ -133,7 +147,7 @@ export declare class View extends BasePgModel implements TableLikeObject {
|
|
|
133
147
|
has_triggers: boolean;
|
|
134
148
|
has_subclasses: boolean;
|
|
135
149
|
is_populated: boolean;
|
|
136
|
-
replica_identity: "
|
|
150
|
+
replica_identity: "n" | "i" | "d" | "f";
|
|
137
151
|
is_partition: boolean;
|
|
138
152
|
options: string[] | null;
|
|
139
153
|
partition_bound: string | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sql } from "@ts-safeql/sql-tag";
|
|
2
2
|
import z from "zod";
|
|
3
|
-
import { BasePgModel, columnPropsSchema, } from "../base.model.js";
|
|
3
|
+
import { BasePgModel, columnPropsSchema, normalizeColumns, } from "../base.model.js";
|
|
4
4
|
import { privilegePropsSchema, } from "../base.privilege-diff.js";
|
|
5
5
|
import { ReplicaIdentitySchema } from "../table/table.model.js";
|
|
6
6
|
const viewPropsSchema = z.object({
|
|
@@ -95,21 +95,11 @@ export class View extends BasePgModel {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
stableSnapshot() {
|
|
98
|
-
const normalizeColumns = () => [...this.columns]
|
|
99
|
-
.map((col) => {
|
|
100
|
-
const { position: _pos, ...rest } = col;
|
|
101
|
-
return rest;
|
|
102
|
-
})
|
|
103
|
-
.sort((a, b) => {
|
|
104
|
-
const nameA = a.name ?? "";
|
|
105
|
-
const nameB = b.name ?? "";
|
|
106
|
-
return nameA.localeCompare(nameB);
|
|
107
|
-
});
|
|
108
98
|
return {
|
|
109
99
|
identity: this.identityFields,
|
|
110
100
|
data: {
|
|
111
101
|
...this.dataFields,
|
|
112
|
-
columns: normalizeColumns(),
|
|
102
|
+
columns: normalizeColumns(this.columns),
|
|
113
103
|
},
|
|
114
104
|
};
|
|
115
105
|
}
|
|
@@ -218,7 +208,7 @@ select
|
|
|
218
208
|
from (
|
|
219
209
|
-- one row for object ACL + one row per column ACL
|
|
220
210
|
select null::name as attname, v.oid as relacl_oid, (
|
|
221
|
-
select c_rel.relacl from pg_class c_rel where c_rel.oid = v.oid
|
|
211
|
+
select COALESCE(c_rel.relacl, acldefault('r', c_rel.relowner)) from pg_class c_rel where c_rel.oid = v.oid
|
|
222
212
|
) as acl
|
|
223
213
|
union all
|
|
224
214
|
select a2.attname, v.oid as relacl_oid, a2.attacl
|
package/dist/core/plan/apply.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan application - execute migration plans against target databases.
|
|
3
3
|
*/
|
|
4
|
-
import { escapeIdentifier } from "pg";
|
|
5
4
|
import { diffCatalogs } from "../catalog.diff.js";
|
|
6
5
|
import { extractCatalog } from "../catalog.model.js";
|
|
7
6
|
import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.js";
|
|
8
7
|
import { compileFilterDSL } from "../integrations/filter/dsl.js";
|
|
9
|
-
import {
|
|
8
|
+
import { createManagedPool, endPool } from "../postgres-config.js";
|
|
10
9
|
import { sortChanges } from "../sort/sort-changes.js";
|
|
11
10
|
/**
|
|
12
11
|
* Check if a statement is a session configuration statement (standalone SET statements).
|
|
@@ -30,39 +29,23 @@ export async function applyPlan(plan, source, target, options = {}) {
|
|
|
30
29
|
let desiredPool;
|
|
31
30
|
let shouldCloseCurrent = false;
|
|
32
31
|
let shouldCloseDesired = false;
|
|
33
|
-
// Suppress expected shutdown errors from idle pool connections (57P01 = admin_shutdown)
|
|
34
|
-
const onError = (err) => {
|
|
35
|
-
if (err.code !== "57P01") {
|
|
36
|
-
console.error("Pool error:", err);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
32
|
if (typeof source === "string") {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Force fully qualified names in catalog queries for fingerprint verification
|
|
44
|
-
await client.query("SET search_path = ''");
|
|
45
|
-
if (plan.role) {
|
|
46
|
-
await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
33
|
+
const managed = await createManagedPool(source, {
|
|
34
|
+
role: plan.role,
|
|
35
|
+
label: "source",
|
|
49
36
|
});
|
|
37
|
+
currentPool = managed.pool;
|
|
50
38
|
shouldCloseCurrent = true;
|
|
51
39
|
}
|
|
52
40
|
else {
|
|
53
41
|
currentPool = source;
|
|
54
42
|
}
|
|
55
43
|
if (typeof target === "string") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Force fully qualified names in catalog queries for fingerprint verification
|
|
60
|
-
await client.query("SET search_path = ''");
|
|
61
|
-
if (plan.role) {
|
|
62
|
-
await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
44
|
+
const managed = await createManagedPool(target, {
|
|
45
|
+
role: plan.role,
|
|
46
|
+
label: "target",
|
|
65
47
|
});
|
|
48
|
+
desiredPool = managed.pool;
|
|
66
49
|
shouldCloseDesired = true;
|
|
67
50
|
}
|
|
68
51
|
else {
|
|
@@ -2,21 +2,34 @@
|
|
|
2
2
|
* Plan creation - the main entry point for creating migration plans.
|
|
3
3
|
*/
|
|
4
4
|
import type { Pool } from "pg";
|
|
5
|
+
import { Catalog } from "../catalog.model.ts";
|
|
5
6
|
import type { Change } from "../change.types.ts";
|
|
6
7
|
import type { DiffContext } from "../context.ts";
|
|
7
8
|
import type { CreatePlanOptions, Plan } from "./types.ts";
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* Input for source/target: a postgres connection URL, an existing Pool, or
|
|
11
|
+
* an already-resolved Catalog (e.g. deserialized from a snapshot file).
|
|
12
|
+
*/
|
|
13
|
+
export type CatalogInput = string | Pool | Catalog;
|
|
14
|
+
/**
|
|
15
|
+
* Create a migration plan by comparing two catalog states.
|
|
16
|
+
*
|
|
17
|
+
* Each input can be:
|
|
18
|
+
* - A postgres connection URL (string) -- a pool is created and catalog extracted
|
|
19
|
+
* - An existing pg Pool -- catalog is extracted directly
|
|
20
|
+
* - A Catalog instance -- used as-is (e.g. from a deserialized snapshot)
|
|
21
|
+
*
|
|
22
|
+
* When `source` is `null`, a minimal empty catalog (`createEmptyCatalog`) is
|
|
23
|
+
* used as the baseline. For a more accurate baseline, pass a Catalog
|
|
24
|
+
* deserialized from a snapshot of `template1` or another reference database.
|
|
10
25
|
*
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
26
|
+
* @param source - Source catalog input (current state), or null for empty baseline
|
|
27
|
+
* @param target - Target catalog input (desired state)
|
|
13
28
|
* @param options - Optional configuration
|
|
14
29
|
* @returns A Plan if there are changes, null if databases are identical
|
|
15
30
|
*/
|
|
16
|
-
|
|
17
|
-
export declare function createPlan(source: ConnectionInput, target: ConnectionInput, options?: CreatePlanOptions): Promise<{
|
|
31
|
+
export declare function createPlan(source: CatalogInput | null, target: CatalogInput, options?: CreatePlanOptions): Promise<{
|
|
18
32
|
plan: Plan;
|
|
19
33
|
sortedChanges: Change[];
|
|
20
34
|
ctx: DiffContext;
|
|
21
35
|
} | null>;
|
|
22
|
-
export {};
|
package/dist/core/plan/create.js
CHANGED
|
@@ -1,196 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan creation - the main entry point for creating migration plans.
|
|
3
3
|
*/
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
4
|
import { escapeIdentifier } from "pg";
|
|
6
5
|
import { diffCatalogs } from "../catalog.diff.js";
|
|
7
|
-
import { extractCatalog } from "../catalog.model.js";
|
|
6
|
+
import { Catalog, createEmptyCatalog, extractCatalog, } from "../catalog.model.js";
|
|
8
7
|
import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.js";
|
|
9
8
|
import { compileFilterDSL, } from "../integrations/filter/dsl.js";
|
|
10
9
|
import { compileSerializeDSL, } from "../integrations/serialize/dsl.js";
|
|
11
|
-
import {
|
|
10
|
+
import { createManagedPool, endPool } from "../postgres-config.js";
|
|
12
11
|
import { sortChanges } from "../sort/sort-changes.js";
|
|
13
12
|
import { classifyChangesRisk } from "./risk.js";
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
14
|
+
* Create a migration plan by comparing two catalog states.
|
|
15
|
+
*
|
|
16
|
+
* Each input can be:
|
|
17
|
+
* - A postgres connection URL (string) -- a pool is created and catalog extracted
|
|
18
|
+
* - An existing pg Pool -- catalog is extracted directly
|
|
19
|
+
* - A Catalog instance -- used as-is (e.g. from a deserialized snapshot)
|
|
20
|
+
*
|
|
21
|
+
* When `source` is `null`, a minimal empty catalog (`createEmptyCatalog`) is
|
|
22
|
+
* used as the baseline. For a more accurate baseline, pass a Catalog
|
|
23
|
+
* deserialized from a snapshot of `template1` or another reference database.
|
|
24
|
+
*
|
|
25
|
+
* @param source - Source catalog input (current state), or null for empty baseline
|
|
26
|
+
* @param target - Target catalog input (desired state)
|
|
27
|
+
* @param options - Optional configuration
|
|
28
|
+
* @returns A Plan if there are changes, null if databases are identical
|
|
21
29
|
*/
|
|
22
|
-
async function parseSslConfig(url, connectionType) {
|
|
23
|
-
const urlObj = new URL(url);
|
|
24
|
-
const sslmode = urlObj.searchParams.get("sslmode");
|
|
25
|
-
const sslrootcert = urlObj.searchParams.get("sslrootcert");
|
|
26
|
-
const sslcert = urlObj.searchParams.get("sslcert");
|
|
27
|
-
const sslkey = urlObj.searchParams.get("sslkey");
|
|
28
|
-
// Remove SSL-related query parameters since we parse them ourselves
|
|
29
|
-
urlObj.searchParams.delete("sslmode");
|
|
30
|
-
urlObj.searchParams.delete("sslrootcert");
|
|
31
|
-
urlObj.searchParams.delete("sslcert");
|
|
32
|
-
urlObj.searchParams.delete("sslkey");
|
|
33
|
-
const cleanedUrl = urlObj.toString();
|
|
34
|
-
// Handle different SSL modes
|
|
35
|
-
if (sslmode === "disable") {
|
|
36
|
-
return { cleanedUrl, ssl: false };
|
|
37
|
-
}
|
|
38
|
-
if (sslmode === "require" ||
|
|
39
|
-
sslmode === "prefer" ||
|
|
40
|
-
sslmode === "verify-ca" ||
|
|
41
|
-
sslmode === "verify-full") {
|
|
42
|
-
// Helper function to get certificate value: query param (file path) takes precedence over env var (content)
|
|
43
|
-
const getCertValue = async (queryParam, envVarName) => {
|
|
44
|
-
// Prefer query parameter (file path)
|
|
45
|
-
if (queryParam) {
|
|
46
|
-
try {
|
|
47
|
-
return await readFile(queryParam, "utf-8");
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
throw new Error(`Failed to read certificate file '${queryParam}': ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// Fallback to environment variable (content)
|
|
54
|
-
const envValue = process.env[envVarName];
|
|
55
|
-
return envValue || undefined;
|
|
56
|
-
};
|
|
57
|
-
const hasExplicitVerification = sslmode === "verify-ca" || sslmode === "verify-full";
|
|
58
|
-
// Get CA certificate value.
|
|
59
|
-
// - verify-ca/verify-full: check query param first, then env var
|
|
60
|
-
// - require/prefer: only check query param (libpq backward compatibility
|
|
61
|
-
// requires an explicit root CA *file*, not a global env var)
|
|
62
|
-
const caEnvVar = connectionType === "source"
|
|
63
|
-
? "PGDELTA_SOURCE_SSLROOTCERT"
|
|
64
|
-
: "PGDELTA_TARGET_SSLROOTCERT";
|
|
65
|
-
let caValue;
|
|
66
|
-
if (sslrootcert) {
|
|
67
|
-
// Explicit file path in query param — always honour it
|
|
68
|
-
caValue = await getCertValue(sslrootcert, caEnvVar);
|
|
69
|
-
}
|
|
70
|
-
else if (hasExplicitVerification) {
|
|
71
|
-
// verify-ca / verify-full without file path — fall back to env var
|
|
72
|
-
caValue = await getCertValue(null, caEnvVar);
|
|
73
|
-
}
|
|
74
|
-
// require/prefer without sslrootcert: no CA cert, no verification
|
|
75
|
-
// Determine if we should verify the CA chain
|
|
76
|
-
// From PostgreSQL docs: "if a root CA file exists, the behavior of sslmode=require
|
|
77
|
-
// will be the same as that of verify-ca"
|
|
78
|
-
const hasLibpqCompatibility = (sslmode === "require" || sslmode === "prefer") && caValue !== undefined;
|
|
79
|
-
const shouldVerifyCa = hasExplicitVerification || hasLibpqCompatibility;
|
|
80
|
-
// Determine if we should verify hostname
|
|
81
|
-
// - verify-full: verify both CA and hostname
|
|
82
|
-
// - verify-ca: verify CA only (skip hostname)
|
|
83
|
-
// - require/prefer with CA (libpq compat): behaves like verify-ca (skip hostname)
|
|
84
|
-
const shouldVerifyHostname = sslmode === "verify-full";
|
|
85
|
-
const ssl = {
|
|
86
|
-
rejectUnauthorized: shouldVerifyCa,
|
|
87
|
-
};
|
|
88
|
-
// Add CA certificate if verifying
|
|
89
|
-
if (shouldVerifyCa && caValue) {
|
|
90
|
-
ssl.ca = caValue;
|
|
91
|
-
}
|
|
92
|
-
// For verify-ca and libpq compatibility mode: skip hostname verification
|
|
93
|
-
// This matches PostgreSQL semantics where verify-ca only checks the CA chain
|
|
94
|
-
if (shouldVerifyCa && !shouldVerifyHostname) {
|
|
95
|
-
ssl.checkServerIdentity = () => undefined;
|
|
96
|
-
}
|
|
97
|
-
// Get client certificate (optional, for mutual TLS)
|
|
98
|
-
const certEnvVar = connectionType === "source"
|
|
99
|
-
? "PGDELTA_SOURCE_SSLCERT"
|
|
100
|
-
: "PGDELTA_TARGET_SSLCERT";
|
|
101
|
-
const certValue = await getCertValue(sslcert, certEnvVar);
|
|
102
|
-
if (certValue) {
|
|
103
|
-
ssl.cert = certValue;
|
|
104
|
-
}
|
|
105
|
-
// Get client key (optional, for mutual TLS, required if cert is provided)
|
|
106
|
-
const keyEnvVar = connectionType === "source"
|
|
107
|
-
? "PGDELTA_SOURCE_SSLKEY"
|
|
108
|
-
: "PGDELTA_TARGET_SSLKEY";
|
|
109
|
-
const keyValue = await getCertValue(sslkey, keyEnvVar);
|
|
110
|
-
if (keyValue) {
|
|
111
|
-
ssl.key = keyValue;
|
|
112
|
-
}
|
|
113
|
-
// Warn if cert is provided without key (or vice versa)
|
|
114
|
-
if ((ssl.cert && !ssl.key) || (!ssl.cert && ssl.key)) {
|
|
115
|
-
throw new Error("Both client certificate and key must be provided together for mutual TLS");
|
|
116
|
-
}
|
|
117
|
-
return { ssl, cleanedUrl };
|
|
118
|
-
}
|
|
119
|
-
// No sslmode specified or invalid value - explicitly disable SSL
|
|
120
|
-
return { cleanedUrl, ssl: false };
|
|
121
|
-
}
|
|
122
30
|
export async function createPlan(source, target, options = {}) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
console.error("Pool error:", err);
|
|
31
|
+
const resolvePool = async (input, label) => {
|
|
32
|
+
if (typeof input === "string") {
|
|
33
|
+
const managed = await createManagedPool(input, {
|
|
34
|
+
role: options.role,
|
|
35
|
+
label,
|
|
36
|
+
});
|
|
37
|
+
return { pool: managed.pool, shouldClose: true };
|
|
131
38
|
}
|
|
39
|
+
return { pool: input, shouldClose: false };
|
|
132
40
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
shouldCloseSource = true;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
sourcePool = source;
|
|
150
|
-
}
|
|
151
|
-
if (typeof target === "string") {
|
|
152
|
-
const sslConfig = await parseSslConfig(target, "target");
|
|
153
|
-
targetPool = createPool(sslConfig.cleanedUrl, {
|
|
154
|
-
...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
|
|
155
|
-
onError,
|
|
156
|
-
onConnect: async (client) => {
|
|
157
|
-
// Force fully qualified names in catalog queries
|
|
158
|
-
await client.query("SET search_path = ''");
|
|
159
|
-
if (options.role) {
|
|
160
|
-
await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
shouldCloseTarget = true;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
targetPool = target;
|
|
168
|
-
}
|
|
169
|
-
const sourceExtraction = extractCatalog(sourcePool);
|
|
170
|
-
const targetExtraction = extractCatalog(targetPool);
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a CatalogInput to a Catalog, tracking pools that need cleanup.
|
|
43
|
+
*/
|
|
44
|
+
const resolveCatalog = async (input, label, pools) => {
|
|
45
|
+
if (input instanceof Catalog) {
|
|
46
|
+
return input;
|
|
47
|
+
}
|
|
48
|
+
const resolved = await resolvePool(input, label);
|
|
49
|
+
pools.push(resolved);
|
|
50
|
+
return extractCatalog(resolved.pool);
|
|
51
|
+
};
|
|
52
|
+
const pools = [];
|
|
171
53
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
54
|
+
const toCatalog = await resolveCatalog(target, "target", pools);
|
|
55
|
+
const fromCatalog = source !== null
|
|
56
|
+
? await resolveCatalog(source, "source", pools)
|
|
57
|
+
: await createEmptyCatalog(toCatalog.version, toCatalog.currentUser);
|
|
176
58
|
return buildPlanForCatalogs(fromCatalog, toCatalog, options);
|
|
177
59
|
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
// When one extraction fails, the other may still have in-flight queries.
|
|
180
|
-
// Wait for both to settle before pool cleanup to prevent unhandled
|
|
181
|
-
// rejections from connections being terminated mid-flight.
|
|
182
|
-
await Promise.allSettled([sourceExtraction, targetExtraction]);
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
185
60
|
finally {
|
|
186
|
-
const closers =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
closers.push(endPool(targetPool));
|
|
191
|
-
if (closers.length) {
|
|
61
|
+
const closers = pools
|
|
62
|
+
.filter((p) => p.shouldClose)
|
|
63
|
+
.map((p) => endPool(p.pool));
|
|
64
|
+
if (closers.length)
|
|
192
65
|
await Promise.all(closers);
|
|
193
|
-
}
|
|
194
66
|
}
|
|
195
67
|
}
|
|
196
68
|
/**
|
|
@@ -199,6 +71,7 @@ export async function createPlan(source, target, options = {}) {
|
|
|
199
71
|
function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
|
|
200
72
|
const changes = diffCatalogs(fromCatalog, toCatalog, {
|
|
201
73
|
role: options.role,
|
|
74
|
+
skipDefaultPrivilegeSubtraction: options.skipDefaultPrivilegeSubtraction,
|
|
202
75
|
});
|
|
203
76
|
const filterOption = options.filter;
|
|
204
77
|
const serializeOption = options.serialize;
|
|
@@ -231,9 +104,18 @@ function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
|
|
|
231
104
|
}
|
|
232
105
|
// Use filter from final integration
|
|
233
106
|
const filterFn = finalIntegration?.filter;
|
|
234
|
-
|
|
107
|
+
let filteredChanges = filterFn
|
|
235
108
|
? changes.filter((change) => filterFn(change))
|
|
236
109
|
: changes;
|
|
110
|
+
// Cascade dependency exclusions: when a change is excluded by the filter,
|
|
111
|
+
// also exclude changes that depend on it (via requires or pg_depend).
|
|
112
|
+
// DSL filters: cascade only if explicitly opted in (cascade: true). Function filters: cascade by default.
|
|
113
|
+
const shouldCascade = isFilterDSL
|
|
114
|
+
? filterDSL?.cascade === true
|
|
115
|
+
: true;
|
|
116
|
+
if (filterFn && filteredChanges.length < changes.length && shouldCascade) {
|
|
117
|
+
filteredChanges = cascadeExclusions(filteredChanges, changes, toCatalog.depends);
|
|
118
|
+
}
|
|
237
119
|
if (filteredChanges.length === 0) {
|
|
238
120
|
return null;
|
|
239
121
|
}
|
|
@@ -242,6 +124,84 @@ function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
|
|
|
242
124
|
return { plan, sortedChanges, ctx };
|
|
243
125
|
}
|
|
244
126
|
// ============================================================================
|
|
127
|
+
// Dependency Cascading
|
|
128
|
+
// ============================================================================
|
|
129
|
+
/**
|
|
130
|
+
* Cascade exclusions through dependency relationships.
|
|
131
|
+
*
|
|
132
|
+
* When a change is excluded by the filter, any change that depends on it
|
|
133
|
+
* (via explicit `requires` or via catalog `pg_depend`) should also be excluded.
|
|
134
|
+
* This runs as a fixpoint loop, bounded by the total number of changes to
|
|
135
|
+
* guarantee deterministic termination.
|
|
136
|
+
*
|
|
137
|
+
* @param filteredChanges - Changes that passed the initial filter
|
|
138
|
+
* @param allChanges - All changes before filtering
|
|
139
|
+
* @param catalogDepends - Dependency rows from the target catalog (pg_depend)
|
|
140
|
+
* @returns The filtered changes with cascading exclusions applied
|
|
141
|
+
*/
|
|
142
|
+
function cascadeExclusions(filteredChanges, allChanges, catalogDepends) {
|
|
143
|
+
// Collect stableIds created by initially-excluded changes
|
|
144
|
+
const filteredSet = new Set(filteredChanges);
|
|
145
|
+
const excludedIds = new Set();
|
|
146
|
+
for (const change of allChanges) {
|
|
147
|
+
if (!filteredSet.has(change)) {
|
|
148
|
+
for (const id of change.creates ?? []) {
|
|
149
|
+
excludedIds.add(id);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (excludedIds.size === 0) {
|
|
154
|
+
return filteredChanges;
|
|
155
|
+
}
|
|
156
|
+
// Build reverse dependency map: referenced_stable_id -> Set(dependent_stable_ids)
|
|
157
|
+
const catalogDependents = new Map();
|
|
158
|
+
for (const dep of catalogDepends) {
|
|
159
|
+
const existing = catalogDependents.get(dep.referenced_stable_id);
|
|
160
|
+
if (existing) {
|
|
161
|
+
existing.add(dep.dependent_stable_id);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
catalogDependents.set(dep.referenced_stable_id, new Set([dep.dependent_stable_id]));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Fixpoint loop: bounded by total changes to guarantee termination.
|
|
168
|
+
// Each iteration must remove at least one change, otherwise we break.
|
|
169
|
+
let result = filteredChanges;
|
|
170
|
+
for (let i = 0; i < allChanges.length; i++) {
|
|
171
|
+
const beforeLength = result.length;
|
|
172
|
+
result = result.filter((change) => {
|
|
173
|
+
// Check explicit requirements: does this change require an excluded id?
|
|
174
|
+
const requires = change.requires ?? [];
|
|
175
|
+
if (requires.some((dep) => excludedIds.has(dep))) {
|
|
176
|
+
for (const id of change.creates ?? []) {
|
|
177
|
+
excludedIds.add(id);
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
// Check catalog dependencies: does anything this change creates
|
|
182
|
+
// depend on an excluded id via pg_depend?
|
|
183
|
+
const creates = change.creates ?? [];
|
|
184
|
+
for (const createdId of creates) {
|
|
185
|
+
for (const excludedId of excludedIds) {
|
|
186
|
+
const dependents = catalogDependents.get(excludedId);
|
|
187
|
+
if (dependents?.has(createdId)) {
|
|
188
|
+
for (const id of creates) {
|
|
189
|
+
excludedIds.add(id);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
});
|
|
197
|
+
// No changes removed this iteration — fixpoint reached
|
|
198
|
+
if (result.length === beforeLength) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
// ============================================================================
|
|
245
205
|
// Plan Building
|
|
246
206
|
// ============================================================================
|
|
247
207
|
/**
|
|
@@ -139,10 +139,22 @@ export function getParentInfo(change) {
|
|
|
139
139
|
const parentType = change.index.table_relkind === "m" ? "materialized_view" : "table";
|
|
140
140
|
return { type: parentType, name: change.index.table_name };
|
|
141
141
|
}
|
|
142
|
-
case "trigger":
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
case "trigger": {
|
|
143
|
+
const parentType = change.trigger.table_relkind === "v"
|
|
144
|
+
? "view"
|
|
145
|
+
: change.trigger.table_relkind === "m"
|
|
146
|
+
? "materialized_view"
|
|
147
|
+
: "table";
|
|
148
|
+
return { type: parentType, name: change.trigger.table_name };
|
|
149
|
+
}
|
|
150
|
+
case "rule": {
|
|
151
|
+
const parentType = change.rule.relation_kind === "v"
|
|
152
|
+
? "view"
|
|
153
|
+
: change.rule.relation_kind === "m"
|
|
154
|
+
? "materialized_view"
|
|
155
|
+
: "table";
|
|
156
|
+
return { type: parentType, name: change.rule.table_name };
|
|
157
|
+
}
|
|
146
158
|
case "rls_policy":
|
|
147
159
|
return { type: "table", name: change.policy.table_name };
|
|
148
160
|
case "aggregate":
|
|
@@ -84,7 +84,7 @@ import { CreateProcedure } from "../../objects/procedure/changes/procedure.creat
|
|
|
84
84
|
import { DropProcedure } from "../../objects/procedure/changes/procedure.drop.js";
|
|
85
85
|
import { GrantProcedurePrivileges, RevokeGrantOptionProcedurePrivileges, RevokeProcedurePrivileges, } from "../../objects/procedure/changes/procedure.privilege.js";
|
|
86
86
|
import { Procedure } from "../../objects/procedure/procedure.model.js";
|
|
87
|
-
import { AlterPublicationAddSchemas, AlterPublicationAddTables, AlterPublicationDropSchemas, AlterPublicationDropTables,
|
|
87
|
+
import { AlterPublicationAddSchemas, AlterPublicationAddTables, AlterPublicationDropSchemas, AlterPublicationDropTables, AlterPublicationSetList, AlterPublicationSetOptions, AlterPublicationSetOwner, } from "../../objects/publication/changes/publication.alter.js";
|
|
88
88
|
import { CreateCommentOnPublication, DropCommentOnPublication, } from "../../objects/publication/changes/publication.comment.js";
|
|
89
89
|
// ── Publication changes ─────────────────────────────────────────────────────
|
|
90
90
|
import { CreatePublication } from "../../objects/publication/changes/publication.create.js";
|
|
@@ -769,6 +769,7 @@ const trigger = new Trigger({
|
|
|
769
769
|
schema: "public",
|
|
770
770
|
name: "trg_audit",
|
|
771
771
|
table_name: "table_with_very_long_name_for_formatting_and_wrapping_test",
|
|
772
|
+
table_relkind: "r",
|
|
772
773
|
function_schema: "public",
|
|
773
774
|
function_name: "audit_trigger_fn",
|
|
774
775
|
trigger_type: 7,
|
|
@@ -931,6 +932,7 @@ const role = new Role({
|
|
|
931
932
|
objtype: "r",
|
|
932
933
|
grantee: "app_reader",
|
|
933
934
|
privileges: [{ privilege: "SELECT", grantable: false }],
|
|
935
|
+
is_implicit: false,
|
|
934
936
|
},
|
|
935
937
|
],
|
|
936
938
|
});
|
|
@@ -1572,10 +1574,6 @@ const changeCases = [
|
|
|
1572
1574
|
setPublishViaPartitionRoot: true,
|
|
1573
1575
|
}),
|
|
1574
1576
|
},
|
|
1575
|
-
{
|
|
1576
|
-
label: "publication.alter.set_all_tables",
|
|
1577
|
-
change: new AlterPublicationSetForAllTables({ publication }),
|
|
1578
|
-
},
|
|
1579
1577
|
{
|
|
1580
1578
|
label: "publication.alter.set_list",
|
|
1581
1579
|
change: new AlterPublicationSetList({ publication }),
|