@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
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { AlterRoleSetOptions } from "./changes/role.alter.ts";
|
|
3
3
|
import { CreateRole } from "./changes/role.create.ts";
|
|
4
4
|
import { DropRole } from "./changes/role.drop.ts";
|
|
5
|
+
import { GrantRoleMembership } from "./changes/role.privilege.ts";
|
|
5
6
|
import { diffRoles } from "./role.diff.ts";
|
|
6
7
|
import { Role, type RoleProps } from "./role.model.ts";
|
|
7
8
|
|
|
@@ -41,4 +42,238 @@ describe.concurrent("role.diff", () => {
|
|
|
41
42
|
);
|
|
42
43
|
expect(changes[0]).toBeInstanceOf(AlterRoleSetOptions);
|
|
43
44
|
});
|
|
45
|
+
|
|
46
|
+
test("no duplicate membership grants when members have multiple grantors", () => {
|
|
47
|
+
// In PG 16+, pg_auth_members can have multiple rows for the same
|
|
48
|
+
// (roleid, member) pair with different grantors. The Role constructor
|
|
49
|
+
// should deduplicate these so diffRoles doesn't emit duplicate changes.
|
|
50
|
+
const parentRole = new Role({
|
|
51
|
+
...base,
|
|
52
|
+
name: "postgres",
|
|
53
|
+
members: [
|
|
54
|
+
{
|
|
55
|
+
member: "cli_login_postgres",
|
|
56
|
+
grantor: "postgres",
|
|
57
|
+
admin_option: false,
|
|
58
|
+
inherit_option: false,
|
|
59
|
+
set_option: false,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
member: "cli_login_postgres",
|
|
63
|
+
grantor: "supabase_admin",
|
|
64
|
+
admin_option: false,
|
|
65
|
+
inherit_option: false,
|
|
66
|
+
set_option: false,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// After deduplication, should have only one member entry
|
|
72
|
+
expect(parentRole.members).toHaveLength(1);
|
|
73
|
+
expect(parentRole.members[0].member).toBe("cli_login_postgres");
|
|
74
|
+
|
|
75
|
+
// When diffing, the created role should emit only one GRANT
|
|
76
|
+
const changes = diffRoles(
|
|
77
|
+
{ version: 170000 },
|
|
78
|
+
{},
|
|
79
|
+
{ [parentRole.stableId]: parentRole },
|
|
80
|
+
);
|
|
81
|
+
const grantChanges = changes.filter(
|
|
82
|
+
(c) => c instanceof GrantRoleMembership,
|
|
83
|
+
);
|
|
84
|
+
expect(grantChanges).toHaveLength(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("duplicate members are merged with most permissive options", () => {
|
|
88
|
+
const role = new Role({
|
|
89
|
+
...base,
|
|
90
|
+
name: "test_role",
|
|
91
|
+
members: [
|
|
92
|
+
{
|
|
93
|
+
member: "member1",
|
|
94
|
+
grantor: "grantor_a",
|
|
95
|
+
admin_option: false,
|
|
96
|
+
inherit_option: false,
|
|
97
|
+
set_option: true,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
member: "member1",
|
|
101
|
+
grantor: "grantor_b",
|
|
102
|
+
admin_option: true,
|
|
103
|
+
inherit_option: false,
|
|
104
|
+
set_option: false,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(role.members).toHaveLength(1);
|
|
110
|
+
expect(role.members[0].admin_option).toBe(true);
|
|
111
|
+
expect(role.members[0].set_option).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("no false alter when both sides have duplicate members from different grantors", () => {
|
|
115
|
+
// Both main and branch have the same membership but from different
|
|
116
|
+
// grantors. After deduplication the roles should be equal, producing
|
|
117
|
+
// no changes.
|
|
118
|
+
const main = new Role({
|
|
119
|
+
...base,
|
|
120
|
+
name: "parent",
|
|
121
|
+
members: [
|
|
122
|
+
{
|
|
123
|
+
member: "child",
|
|
124
|
+
grantor: "postgres",
|
|
125
|
+
admin_option: false,
|
|
126
|
+
inherit_option: false,
|
|
127
|
+
set_option: false,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
member: "child",
|
|
131
|
+
grantor: "supabase_admin",
|
|
132
|
+
admin_option: false,
|
|
133
|
+
inherit_option: false,
|
|
134
|
+
set_option: false,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
const branch = new Role({
|
|
139
|
+
...base,
|
|
140
|
+
name: "parent",
|
|
141
|
+
members: [
|
|
142
|
+
{
|
|
143
|
+
member: "child",
|
|
144
|
+
grantor: "another_admin",
|
|
145
|
+
admin_option: false,
|
|
146
|
+
inherit_option: false,
|
|
147
|
+
set_option: false,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const changes = diffRoles(
|
|
153
|
+
{ version: 170000 },
|
|
154
|
+
{ [main.stableId]: main },
|
|
155
|
+
{ [branch.stableId]: branch },
|
|
156
|
+
);
|
|
157
|
+
expect(changes).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("create role skips self-granted membership (member === grantor)", () => {
|
|
161
|
+
// Simulates the auto-created membership when postgres creates a role:
|
|
162
|
+
// PostgreSQL automatically makes the creator a member with grantor=self.
|
|
163
|
+
const role = new Role({
|
|
164
|
+
...base,
|
|
165
|
+
name: "developer",
|
|
166
|
+
members: [
|
|
167
|
+
{
|
|
168
|
+
member: "postgres",
|
|
169
|
+
grantor: "postgres",
|
|
170
|
+
admin_option: true,
|
|
171
|
+
inherit_option: true,
|
|
172
|
+
set_option: true,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
const changes = diffRoles(
|
|
177
|
+
{ version: 170000 },
|
|
178
|
+
{},
|
|
179
|
+
{ [role.stableId]: role },
|
|
180
|
+
);
|
|
181
|
+
// Should only have CreateRole, no GrantRoleMembership for postgres
|
|
182
|
+
expect(changes).toHaveLength(1);
|
|
183
|
+
expect(changes[0]).toBeInstanceOf(CreateRole);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("create role keeps membership when member differs from grantor", () => {
|
|
187
|
+
const role = new Role({
|
|
188
|
+
...base,
|
|
189
|
+
name: "developer",
|
|
190
|
+
members: [
|
|
191
|
+
{
|
|
192
|
+
member: "app_user",
|
|
193
|
+
grantor: "postgres",
|
|
194
|
+
admin_option: true,
|
|
195
|
+
inherit_option: true,
|
|
196
|
+
set_option: true,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
const changes = diffRoles(
|
|
201
|
+
{ version: 170000 },
|
|
202
|
+
{},
|
|
203
|
+
{ [role.stableId]: role },
|
|
204
|
+
);
|
|
205
|
+
// Should have CreateRole + GrantRoleMembership
|
|
206
|
+
expect(changes).toHaveLength(2);
|
|
207
|
+
expect(changes[0]).toBeInstanceOf(CreateRole);
|
|
208
|
+
expect(changes[1]).toBeInstanceOf(GrantRoleMembership);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("create role keeps mixed-grantor membership where not all grantors equal member", () => {
|
|
212
|
+
// Model dedup should prefer the non-self grantor, so diff keeps the membership
|
|
213
|
+
const role = new Role({
|
|
214
|
+
...base,
|
|
215
|
+
name: "developer",
|
|
216
|
+
members: [
|
|
217
|
+
{
|
|
218
|
+
member: "postgres",
|
|
219
|
+
grantor: "postgres",
|
|
220
|
+
admin_option: false,
|
|
221
|
+
inherit_option: true,
|
|
222
|
+
set_option: true,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
member: "postgres",
|
|
226
|
+
grantor: "supabase_admin",
|
|
227
|
+
admin_option: true,
|
|
228
|
+
inherit_option: true,
|
|
229
|
+
set_option: true,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
});
|
|
233
|
+
const changes = diffRoles(
|
|
234
|
+
{ version: 170000 },
|
|
235
|
+
{},
|
|
236
|
+
{ [role.stableId]: role },
|
|
237
|
+
);
|
|
238
|
+
// One grantor is different from member, dedup prefers it → membership kept
|
|
239
|
+
expect(changes).toHaveLength(2);
|
|
240
|
+
expect(changes[0]).toBeInstanceOf(CreateRole);
|
|
241
|
+
expect(changes[1]).toBeInstanceOf(GrantRoleMembership);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("alter role skips granting admin to self-granted membership", () => {
|
|
245
|
+
const mainRole = new Role({
|
|
246
|
+
...base,
|
|
247
|
+
name: "developer",
|
|
248
|
+
members: [
|
|
249
|
+
{
|
|
250
|
+
member: "postgres",
|
|
251
|
+
grantor: "postgres",
|
|
252
|
+
admin_option: false,
|
|
253
|
+
inherit_option: true,
|
|
254
|
+
set_option: true,
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
const branchRole = new Role({
|
|
259
|
+
...base,
|
|
260
|
+
name: "developer",
|
|
261
|
+
members: [
|
|
262
|
+
{
|
|
263
|
+
member: "postgres",
|
|
264
|
+
grantor: "postgres",
|
|
265
|
+
admin_option: true,
|
|
266
|
+
inherit_option: true,
|
|
267
|
+
set_option: true,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
const changes = diffRoles(
|
|
272
|
+
{ version: 170000 },
|
|
273
|
+
{ [mainRole.stableId]: mainRole },
|
|
274
|
+
{ [branchRole.stableId]: branchRole },
|
|
275
|
+
);
|
|
276
|
+
// Should produce no changes — granting admin back to self would fail
|
|
277
|
+
expect(changes).toHaveLength(0);
|
|
278
|
+
});
|
|
44
279
|
});
|
|
@@ -51,8 +51,15 @@ export function diffRoles(
|
|
|
51
51
|
if (role.comment !== null) {
|
|
52
52
|
changes.push(new CreateCommentOnRole({ role }));
|
|
53
53
|
}
|
|
54
|
-
// MEMBERSHIPS: Grant memberships immediately after role creation
|
|
54
|
+
// MEMBERSHIPS: Grant memberships immediately after role creation.
|
|
55
|
+
// Members are already deduplicated by the Role model constructor.
|
|
55
56
|
for (const membership of role.members) {
|
|
57
|
+
// Skip memberships where the member is the grantor (auto-created by
|
|
58
|
+
// CREATE ROLE — re-granting them, especially WITH ADMIN OPTION, fails
|
|
59
|
+
// with "ADMIN option cannot be granted back to your own grantor").
|
|
60
|
+
if (membership.grantor === membership.member) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
56
63
|
changes.push(
|
|
57
64
|
new GrantRoleMembership({
|
|
58
65
|
role,
|
|
@@ -67,6 +74,7 @@ export function diffRoles(
|
|
|
67
74
|
}
|
|
68
75
|
// DEFAULT PRIVILEGES: Grant default privileges immediately after role creation
|
|
69
76
|
for (const defaultPriv of role.default_privileges) {
|
|
77
|
+
if (defaultPriv.is_implicit) continue;
|
|
70
78
|
if (defaultPriv.privileges.length === 0) continue;
|
|
71
79
|
const grantGroups = new Map<
|
|
72
80
|
boolean,
|
|
@@ -208,12 +216,19 @@ export function diffRoles(
|
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
// MEMBERSHIPS
|
|
219
|
+
// Members are already deduplicated by the Role model constructor.
|
|
211
220
|
const mainMembers = new Map(mainRole.members.map((m) => [m.member, m]));
|
|
212
221
|
const branchMembers = new Map(branchRole.members.map((m) => [m.member, m]));
|
|
213
222
|
|
|
214
223
|
// Find new members to grant
|
|
215
224
|
for (const [member, membership] of branchMembers) {
|
|
216
225
|
if (!mainMembers.has(member)) {
|
|
226
|
+
// Skip memberships where the member is the grantor (auto-created by
|
|
227
|
+
// CREATE ROLE — re-granting them fails with "ADMIN option cannot be
|
|
228
|
+
// granted back to your own grantor").
|
|
229
|
+
if (membership.grantor === membership.member) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
217
232
|
changes.push(
|
|
218
233
|
new GrantRoleMembership({
|
|
219
234
|
role: branchRole,
|
|
@@ -280,6 +295,11 @@ export function diffRoles(
|
|
|
280
295
|
);
|
|
281
296
|
}
|
|
282
297
|
if (toGrant.admin || toGrant.inherit || toGrant.set) {
|
|
298
|
+
// Skip granting options back to the grantor (same restriction as
|
|
299
|
+
// for newly created roles).
|
|
300
|
+
if (branchMembership.grantor === branchMembership.member) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
283
303
|
changes.push(
|
|
284
304
|
new GrantRoleMembership({
|
|
285
305
|
role: branchRole,
|
|
@@ -18,6 +18,7 @@ const defaultPrivilegeSchema = z.object({
|
|
|
18
18
|
privileges: z.array(
|
|
19
19
|
z.object({ privilege: z.string(), grantable: z.boolean() }),
|
|
20
20
|
),
|
|
21
|
+
is_implicit: z.boolean(),
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
const rolePropsSchema = z.object({
|
|
@@ -70,7 +71,7 @@ export class Role extends BasePgModel {
|
|
|
70
71
|
this.can_bypass_rls = props.can_bypass_rls;
|
|
71
72
|
this.config = props.config;
|
|
72
73
|
this.comment = props.comment;
|
|
73
|
-
this.members = props.members;
|
|
74
|
+
this.members = deduplicateMembers(props.members);
|
|
74
75
|
this.default_privileges = props.default_privileges;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -95,15 +96,18 @@ export class Role extends BasePgModel {
|
|
|
95
96
|
);
|
|
96
97
|
});
|
|
97
98
|
|
|
98
|
-
const sortedDefaultPrivs = [...this.default_privileges].map((dp) =>
|
|
99
|
-
...dp
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
const sortedDefaultPrivs = [...this.default_privileges].map((dp) => {
|
|
100
|
+
const { is_implicit: _, ...rest } = dp;
|
|
101
|
+
return {
|
|
102
|
+
...rest,
|
|
103
|
+
privileges: [...dp.privileges].sort((a, b) => {
|
|
104
|
+
return (
|
|
105
|
+
a.privilege.localeCompare(b.privilege) ||
|
|
106
|
+
Number(a.grantable) - Number(b.grantable)
|
|
107
|
+
);
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
107
111
|
sortedDefaultPrivs.sort((a, b) => {
|
|
108
112
|
return (
|
|
109
113
|
(a.in_schema ?? "").localeCompare(b.in_schema ?? "") ||
|
|
@@ -129,6 +133,48 @@ export class Role extends BasePgModel {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Deduplicate members by member name.
|
|
138
|
+
*
|
|
139
|
+
* In PostgreSQL 16+, `pg_auth_members` can have multiple rows for the same
|
|
140
|
+
* (roleid, member) pair with different grantors. Merge them into a single
|
|
141
|
+
* entry per member, combining options with OR so the most permissive wins.
|
|
142
|
+
*
|
|
143
|
+
* When merging, prefer a non-self grantor (grantor !== member) so that
|
|
144
|
+
* downstream code can detect true self-grants (auto-created by CREATE ROLE)
|
|
145
|
+
* by checking `grantor === member`.
|
|
146
|
+
*/
|
|
147
|
+
function deduplicateMembers(
|
|
148
|
+
members: RoleProps["members"],
|
|
149
|
+
): RoleProps["members"] {
|
|
150
|
+
const map = new Map<string, RoleProps["members"][number]>();
|
|
151
|
+
for (const m of members) {
|
|
152
|
+
const existing = map.get(m.member);
|
|
153
|
+
if (existing) {
|
|
154
|
+
// admin_option is always boolean (non-nullable in schema)
|
|
155
|
+
existing.admin_option = existing.admin_option || m.admin_option;
|
|
156
|
+
// inherit_option and set_option are nullish (only available in PG 16+)
|
|
157
|
+
if (m.inherit_option != null) {
|
|
158
|
+
existing.inherit_option =
|
|
159
|
+
(existing.inherit_option ?? false) || m.inherit_option;
|
|
160
|
+
}
|
|
161
|
+
if (m.set_option != null) {
|
|
162
|
+
existing.set_option = (existing.set_option ?? false) || m.set_option;
|
|
163
|
+
}
|
|
164
|
+
// Prefer a non-self grantor so diff can detect true self-grants.
|
|
165
|
+
// Once a non-self grantor is chosen the value is kept (the specific
|
|
166
|
+
// non-self grantor doesn't matter — only the self vs non-self
|
|
167
|
+
// distinction is used downstream).
|
|
168
|
+
if (existing.grantor === existing.member && m.grantor !== m.member) {
|
|
169
|
+
existing.grantor = m.grantor;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
map.set(m.member, { ...m });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return [...map.values()];
|
|
176
|
+
}
|
|
177
|
+
|
|
132
178
|
export async function extractRoles(pool: Pool): Promise<Role[]> {
|
|
133
179
|
// Check PostgreSQL version capabilities for membership options
|
|
134
180
|
const { rows: capabilitiesRows } = await pool.query<{
|
|
@@ -202,13 +248,15 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
|
|
|
202
248
|
THEN 'PUBLIC'
|
|
203
249
|
ELSE s.grantee::regrole::text
|
|
204
250
|
END,
|
|
205
|
-
'privileges', s.privileges
|
|
251
|
+
'privileges', s.privileges,
|
|
252
|
+
'is_implicit', s.is_implicit
|
|
206
253
|
)
|
|
207
254
|
ORDER BY s.defaclnamespace NULLS FIRST,
|
|
208
255
|
s.defaclobjtype,
|
|
209
256
|
s.grantee
|
|
210
257
|
)
|
|
211
258
|
FROM (
|
|
259
|
+
-- Explicit entries from pg_default_acl
|
|
212
260
|
SELECT
|
|
213
261
|
d.defaclnamespace,
|
|
214
262
|
d.defaclobjtype,
|
|
@@ -219,12 +267,41 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
|
|
|
219
267
|
'grantable', x.is_grantable
|
|
220
268
|
)
|
|
221
269
|
ORDER BY x.privilege_type, x.is_grantable
|
|
222
|
-
) AS privileges
|
|
270
|
+
) AS privileges,
|
|
271
|
+
false AS is_implicit
|
|
223
272
|
FROM pg_default_acl d
|
|
224
273
|
CROSS JOIN LATERAL aclexplode(COALESCE(d.defaclacl, ARRAY[]::aclitem[]))
|
|
225
274
|
AS x(grantor, grantee, privilege_type, is_grantable)
|
|
226
275
|
WHERE d.defaclrole = r.oid
|
|
227
276
|
GROUP BY d.defaclnamespace, d.defaclobjtype, x.grantee
|
|
277
|
+
UNION ALL
|
|
278
|
+
-- Implicit defaults from acldefault() for objtypes without a
|
|
279
|
+
-- global pg_default_acl entry. PostgreSQL applies these implicit
|
|
280
|
+
-- defaults (e.g. PUBLIC gets EXECUTE on functions) when no
|
|
281
|
+
-- explicit ALTER DEFAULT PRIVILEGES has been issued. Including
|
|
282
|
+
-- them lets the diff detect REVOKEs of implicit grants.
|
|
283
|
+
SELECT
|
|
284
|
+
0 AS defaclnamespace,
|
|
285
|
+
v.t::"char" AS defaclobjtype,
|
|
286
|
+
x.grantee,
|
|
287
|
+
json_agg(
|
|
288
|
+
json_build_object(
|
|
289
|
+
'privilege', x.privilege_type,
|
|
290
|
+
'grantable', x.is_grantable
|
|
291
|
+
)
|
|
292
|
+
ORDER BY x.privilege_type, x.is_grantable
|
|
293
|
+
) AS privileges,
|
|
294
|
+
true AS is_implicit
|
|
295
|
+
FROM (VALUES ('r'), ('S'), ('f'), ('T'), ('n')) AS v(t)
|
|
296
|
+
CROSS JOIN LATERAL aclexplode(acldefault(v.t::"char", r.oid))
|
|
297
|
+
AS x(grantor, grantee, privilege_type, is_grantable)
|
|
298
|
+
WHERE NOT EXISTS (
|
|
299
|
+
SELECT 1 FROM pg_default_acl d2
|
|
300
|
+
WHERE d2.defaclrole = r.oid
|
|
301
|
+
AND d2.defaclobjtype = v.t::"char"
|
|
302
|
+
AND d2.defaclnamespace = 0
|
|
303
|
+
)
|
|
304
|
+
GROUP BY v.t, x.grantee
|
|
228
305
|
) AS s
|
|
229
306
|
),
|
|
230
307
|
'[]'
|
|
@@ -292,13 +369,15 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
|
|
|
292
369
|
THEN 'PUBLIC'
|
|
293
370
|
ELSE s.grantee::regrole::text
|
|
294
371
|
END,
|
|
295
|
-
'privileges', s.privileges
|
|
372
|
+
'privileges', s.privileges,
|
|
373
|
+
'is_implicit', s.is_implicit
|
|
296
374
|
)
|
|
297
375
|
ORDER BY s.defaclnamespace NULLS FIRST,
|
|
298
376
|
s.defaclobjtype,
|
|
299
377
|
s.grantee
|
|
300
378
|
)
|
|
301
379
|
FROM (
|
|
380
|
+
-- Explicit entries from pg_default_acl
|
|
302
381
|
SELECT
|
|
303
382
|
d.defaclnamespace,
|
|
304
383
|
d.defaclobjtype,
|
|
@@ -309,12 +388,41 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
|
|
|
309
388
|
'grantable', x.is_grantable
|
|
310
389
|
)
|
|
311
390
|
ORDER BY x.privilege_type, x.is_grantable
|
|
312
|
-
) AS privileges
|
|
391
|
+
) AS privileges,
|
|
392
|
+
false AS is_implicit
|
|
313
393
|
FROM pg_default_acl d
|
|
314
394
|
CROSS JOIN LATERAL aclexplode(COALESCE(d.defaclacl, ARRAY[]::aclitem[]))
|
|
315
395
|
AS x(grantor, grantee, privilege_type, is_grantable)
|
|
316
396
|
WHERE d.defaclrole = r.oid
|
|
317
397
|
GROUP BY d.defaclnamespace, d.defaclobjtype, x.grantee
|
|
398
|
+
UNION ALL
|
|
399
|
+
-- Implicit defaults from acldefault() for objtypes without a
|
|
400
|
+
-- global pg_default_acl entry. PostgreSQL applies these implicit
|
|
401
|
+
-- defaults (e.g. PUBLIC gets EXECUTE on functions) when no
|
|
402
|
+
-- explicit ALTER DEFAULT PRIVILEGES has been issued. Including
|
|
403
|
+
-- them lets the diff detect REVOKEs of implicit grants.
|
|
404
|
+
SELECT
|
|
405
|
+
0 AS defaclnamespace,
|
|
406
|
+
v.t::"char" AS defaclobjtype,
|
|
407
|
+
x.grantee,
|
|
408
|
+
json_agg(
|
|
409
|
+
json_build_object(
|
|
410
|
+
'privilege', x.privilege_type,
|
|
411
|
+
'grantable', x.is_grantable
|
|
412
|
+
)
|
|
413
|
+
ORDER BY x.privilege_type, x.is_grantable
|
|
414
|
+
) AS privileges,
|
|
415
|
+
true AS is_implicit
|
|
416
|
+
FROM (VALUES ('r'), ('S'), ('f'), ('T'), ('n')) AS v(t)
|
|
417
|
+
CROSS JOIN LATERAL aclexplode(acldefault(v.t::"char", r.oid))
|
|
418
|
+
AS x(grantor, grantee, privilege_type, is_grantable)
|
|
419
|
+
WHERE NOT EXISTS (
|
|
420
|
+
SELECT 1 FROM pg_default_acl d2
|
|
421
|
+
WHERE d2.defaclrole = r.oid
|
|
422
|
+
AND d2.defaclobjtype = v.t::"char"
|
|
423
|
+
AND d2.defaclnamespace = 0
|
|
424
|
+
)
|
|
425
|
+
GROUP BY v.t, x.grantee
|
|
318
426
|
) AS s
|
|
319
427
|
),
|
|
320
428
|
'[]'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
2
3
|
import { stableId } from "../../utils.ts";
|
|
3
4
|
import { Rule } from "../rule.model.ts";
|
|
4
5
|
import { ReplaceRule, SetRuleEnabledState } from "./rule.alter.ts";
|
|
@@ -28,7 +29,7 @@ const makeRule = (override: Partial<RuleProps> = {}) =>
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
describe("rule.alter", () => {
|
|
31
|
-
test("replace rule serializes using create or replace and tracks dependencies", () => {
|
|
32
|
+
test("replace rule serializes using create or replace and tracks dependencies", async () => {
|
|
32
33
|
const rule = makeRule({ columns: ["id", "amount"] });
|
|
33
34
|
const change = new ReplaceRule({ rule });
|
|
34
35
|
|
|
@@ -39,12 +40,13 @@ describe("rule.alter", () => {
|
|
|
39
40
|
stableId.column(rule.schema, rule.table_name, column),
|
|
40
41
|
),
|
|
41
42
|
]);
|
|
43
|
+
await assertValidSql(change.serialize());
|
|
42
44
|
expect(change.serialize()).toBe(
|
|
43
45
|
'CREATE OR REPLACE RULE "my_rule" AS ON INSERT TO public."my_table" DO INSTEAD NOTHING',
|
|
44
46
|
);
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
test("set rule enabled state serializes appropriate clause", () => {
|
|
49
|
+
test("set rule enabled state serializes appropriate clause", async () => {
|
|
48
50
|
const rule = makeRule({ columns: ["id", "amount"] });
|
|
49
51
|
const change = new SetRuleEnabledState({ rule, enabled: "D" });
|
|
50
52
|
|
|
@@ -55,12 +57,13 @@ describe("rule.alter", () => {
|
|
|
55
57
|
stableId.column(rule.schema, rule.table_name, column),
|
|
56
58
|
),
|
|
57
59
|
]);
|
|
60
|
+
await assertValidSql(change.serialize());
|
|
58
61
|
expect(change.serialize()).toBe(
|
|
59
62
|
'ALTER TABLE public."my_table" DISABLE RULE "my_rule"',
|
|
60
63
|
);
|
|
61
64
|
});
|
|
62
65
|
|
|
63
|
-
test("set rule enabled state defaults to rule value and supports views", () => {
|
|
66
|
+
test("set rule enabled state defaults to rule value and supports views", async () => {
|
|
64
67
|
const rule = makeRule({
|
|
65
68
|
table_name: '"my_view"',
|
|
66
69
|
relation_kind: "v",
|
|
@@ -71,6 +74,7 @@ describe("rule.alter", () => {
|
|
|
71
74
|
const change = new SetRuleEnabledState({ rule });
|
|
72
75
|
|
|
73
76
|
expect(change.requires).toEqual([rule.stableId, rule.relationStableId]);
|
|
77
|
+
await assertValidSql(change.serialize());
|
|
74
78
|
expect(change.serialize()).toBe(
|
|
75
79
|
'ALTER TABLE public."my_view" ENABLE REPLICA RULE "my_rule"',
|
|
76
80
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
2
3
|
import { stableId } from "../../utils.ts";
|
|
3
4
|
import { Rule } from "../rule.model.ts";
|
|
4
5
|
import { CreateCommentOnRule, DropCommentOnRule } from "./rule.comment.ts";
|
|
@@ -28,18 +29,19 @@ const makeRule = (override: Partial<RuleProps> = {}) =>
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
describe("rule.comment", () => {
|
|
31
|
-
test("create comment serializes and tracks dependencies", () => {
|
|
32
|
+
test("create comment serializes and tracks dependencies", async () => {
|
|
32
33
|
const rule = makeRule({ comment: "rule's description" });
|
|
33
34
|
const change = new CreateCommentOnRule({ rule });
|
|
34
35
|
|
|
35
36
|
expect(change.creates).toEqual([stableId.comment(rule.stableId)]);
|
|
36
37
|
expect(change.requires).toEqual([rule.stableId]);
|
|
38
|
+
await assertValidSql(change.serialize());
|
|
37
39
|
expect(change.serialize()).toBe(
|
|
38
40
|
"COMMENT ON RULE \"my_rule\" ON public.\"my_table\" IS 'rule''s description'",
|
|
39
41
|
);
|
|
40
42
|
});
|
|
41
43
|
|
|
42
|
-
test("drop comment serializes and tracks dependencies", () => {
|
|
44
|
+
test("drop comment serializes and tracks dependencies", async () => {
|
|
43
45
|
const rule = makeRule({ comment: "temporary comment" });
|
|
44
46
|
const change = new DropCommentOnRule({ rule });
|
|
45
47
|
|
|
@@ -48,6 +50,7 @@ describe("rule.comment", () => {
|
|
|
48
50
|
stableId.comment(rule.stableId),
|
|
49
51
|
rule.stableId,
|
|
50
52
|
]);
|
|
53
|
+
await assertValidSql(change.serialize());
|
|
51
54
|
expect(change.serialize()).toBe(
|
|
52
55
|
'COMMENT ON RULE "my_rule" ON public."my_table" IS NULL',
|
|
53
56
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
2
3
|
import { stableId } from "../../utils.ts";
|
|
3
4
|
import { Rule } from "../rule.model.ts";
|
|
4
5
|
import { CreateRule } from "./rule.create.ts";
|
|
@@ -28,7 +29,7 @@ const makeRule = (override: Partial<RuleProps> = {}) =>
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
describe("rule.create", () => {
|
|
31
|
-
test("serialize rule definition and track dependencies", () => {
|
|
32
|
+
test("serialize rule definition and track dependencies", async () => {
|
|
32
33
|
const rule = makeRule();
|
|
33
34
|
const change = new CreateRule({ rule });
|
|
34
35
|
|
|
@@ -39,12 +40,13 @@ describe("rule.create", () => {
|
|
|
39
40
|
stableId.column(rule.schema, rule.table_name, column),
|
|
40
41
|
),
|
|
41
42
|
]);
|
|
43
|
+
await assertValidSql(change.serialize());
|
|
42
44
|
expect(change.serialize()).toBe(
|
|
43
45
|
'CREATE RULE "my_rule" AS ON INSERT TO public."my_table" DO INSTEAD NOTHING',
|
|
44
46
|
);
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
test("serialize rule definition with or replace override", () => {
|
|
49
|
+
test("serialize rule definition with or replace override", async () => {
|
|
48
50
|
const rule = makeRule({
|
|
49
51
|
definition:
|
|
50
52
|
' CREATE RULE "my_rule" AS ON INSERT TO public."my_table" DO INSTEAD NOTHING ',
|
|
@@ -52,6 +54,8 @@ describe("rule.create", () => {
|
|
|
52
54
|
|
|
53
55
|
const change = new CreateRule({ rule, orReplace: true });
|
|
54
56
|
|
|
57
|
+
await assertValidSql(change.serialize());
|
|
58
|
+
|
|
55
59
|
expect(change.serialize()).toBe(
|
|
56
60
|
'CREATE OR REPLACE RULE "my_rule" AS ON INSERT TO public."my_table" DO INSTEAD NOTHING',
|
|
57
61
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
2
3
|
import { Rule } from "../rule.model.ts";
|
|
3
4
|
import { DropRule } from "./rule.drop.ts";
|
|
4
5
|
|
|
@@ -27,12 +28,13 @@ const makeRule = (override: Partial<RuleProps> = {}) =>
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
describe("rule.drop", () => {
|
|
30
|
-
test("serialize rule drop and track dependencies", () => {
|
|
31
|
+
test("serialize rule drop and track dependencies", async () => {
|
|
31
32
|
const rule = makeRule();
|
|
32
33
|
const change = new DropRule({ rule });
|
|
33
34
|
|
|
34
35
|
expect(change.drops).toEqual([rule.stableId]);
|
|
35
36
|
expect(change.requires).toEqual([rule.stableId, rule.relationStableId]);
|
|
37
|
+
await assertValidSql(change.serialize());
|
|
36
38
|
expect(change.serialize()).toBe('DROP RULE "my_rule" ON public."my_table"');
|
|
37
39
|
});
|
|
38
40
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
2
3
|
import { Schema, type SchemaProps } from "../schema.model.ts";
|
|
3
4
|
import { AlterSchemaChangeOwner } from "./schema.alter.ts";
|
|
4
5
|
|
|
5
6
|
describe.concurrent("schema", () => {
|
|
6
7
|
describe("alter", () => {
|
|
7
|
-
test("change owner", () => {
|
|
8
|
+
test("change owner", async () => {
|
|
8
9
|
const props: Omit<SchemaProps, "owner"> = {
|
|
9
10
|
name: "test_schema",
|
|
10
11
|
comment: null,
|
|
@@ -20,6 +21,8 @@ describe.concurrent("schema", () => {
|
|
|
20
21
|
owner: "new_owner",
|
|
21
22
|
});
|
|
22
23
|
|
|
24
|
+
await assertValidSql(change.serialize());
|
|
25
|
+
|
|
23
26
|
expect(change.serialize()).toBe(
|
|
24
27
|
"ALTER SCHEMA test_schema OWNER TO new_owner",
|
|
25
28
|
);
|