@safegres/test 0.0.2 → 1.30.7
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 +825 -412
- package/adapters.d.ts +8 -0
- package/adapters.js +18 -0
- package/ast/helpers.d.ts +16 -0
- package/ast/helpers.js +40 -0
- package/ast/parse.d.ts +31 -0
- package/ast/parse.js +87 -0
- package/ast/walk.d.ts +27 -0
- package/ast/walk.js +79 -0
- package/callgraph/baseline.d.ts +28 -0
- package/callgraph/baseline.js +55 -0
- package/callgraph/extract.d.ts +93 -0
- package/callgraph/extract.js +340 -0
- package/callgraph/graph.d.ts +82 -0
- package/callgraph/graph.js +276 -0
- package/checks/anti-patterns.d.ts +53 -0
- package/checks/anti-patterns.js +247 -0
- package/checks/column-grants.d.ts +20 -0
- package/checks/column-grants.js +74 -0
- package/checks/coverage.d.ts +43 -0
- package/checks/coverage.js +110 -0
- package/checks/definer-function.d.ts +91 -0
- package/checks/definer-function.js +458 -0
- package/checks/definer-view.d.ts +167 -0
- package/checks/definer-view.js +392 -0
- package/checks/indexes.d.ts +82 -0
- package/checks/indexes.js +263 -0
- package/checks/lattice.d.ts +179 -0
- package/checks/lattice.js +487 -0
- package/checks/object-acls.d.ts +32 -0
- package/checks/object-acls.js +129 -0
- package/checks/policy-index.d.ts +78 -0
- package/checks/policy-index.js +349 -0
- package/checks/revocable-grants.d.ts +140 -0
- package/checks/revocable-grants.js +377 -0
- package/checks/rls-flags.d.ts +25 -0
- package/checks/rls-flags.js +82 -0
- package/checks/role-reach.d.ts +329 -0
- package/checks/role-reach.js +277 -0
- package/checks/role-trust.d.ts +45 -0
- package/checks/role-trust.js +91 -0
- package/checks/set-role.d.ts +21 -0
- package/checks/set-role.js +66 -0
- package/checks/stats.d.ts +52 -0
- package/checks/stats.js +192 -0
- package/checks/view-exposure.d.ts +68 -0
- package/checks/view-exposure.js +236 -0
- package/checks/view-writes.d.ts +88 -0
- package/checks/view-writes.js +350 -0
- package/ci/baseline.d.ts +104 -0
- package/ci/baseline.js +111 -0
- package/ci/github.d.ts +47 -0
- package/ci/github.js +197 -0
- package/cli/audit.d.ts +3 -0
- package/cli/audit.js +560 -0
- package/cli/baseline.d.ts +3 -0
- package/cli/baseline.js +104 -0
- package/cli/commands.d.ts +2 -0
- package/cli/commands.js +60 -0
- package/cli/doctor.d.ts +3 -0
- package/cli/doctor.js +72 -0
- package/cli/eval.d.ts +3 -0
- package/cli/eval.js +153 -0
- package/cli/print-config.d.ts +3 -0
- package/cli/print-config.js +54 -0
- package/cli/shared.d.ts +43 -0
- package/cli/shared.js +153 -0
- package/cli.d.ts +2 -0
- package/cli.js +69 -0
- package/commands/audit.d.ts +65 -0
- package/commands/audit.js +779 -0
- package/commands/doctor.d.ts +19 -0
- package/commands/doctor.js +181 -0
- package/commands/eval.d.ts +67 -0
- package/commands/eval.js +93 -0
- package/config/fingerprint.d.ts +40 -0
- package/config/fingerprint.js +71 -0
- package/config/loader.d.ts +44 -0
- package/config/loader.js +210 -0
- package/config/presets.d.ts +95 -0
- package/config/presets.js +327 -0
- package/config/resolve.d.ts +47 -0
- package/config/resolve.js +192 -0
- package/config/schema.d.ts +54 -0
- package/config/schema.js +269 -0
- package/config/types.d.ts +562 -0
- package/config/validate.d.ts +14 -0
- package/config/validate.js +125 -0
- package/corpus/README.md +83 -0
- package/corpus/bootstrap.sql +14 -0
- package/corpus/cases/01-anon-write-grant/case.json +26 -0
- package/corpus/cases/01-anon-write-grant/schema.sql +19 -0
- package/corpus/cases/02-grant-to-public/case.json +25 -0
- package/corpus/cases/02-grant-to-public/schema.sql +18 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/case.json +30 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/schema.sql +13 -0
- package/corpus/cases/04-permissive-write-policy/case.json +30 -0
- package/corpus/cases/04-permissive-write-policy/schema.sql +21 -0
- package/corpus/cases/05-public-read-policy/case.json +25 -0
- package/corpus/cases/05-public-read-policy/schema.sql +16 -0
- package/corpus/cases/06-update-without-with-check/case.json +25 -0
- package/corpus/cases/06-update-without-with-check/schema.sql +19 -0
- package/corpus/cases/07-rls-enabled-no-policies/case.json +29 -0
- package/corpus/cases/07-rls-enabled-no-policies/schema.sql +14 -0
- package/corpus/cases/08-select-grant-no-policy/case.json +25 -0
- package/corpus/cases/08-select-grant-no-policy/schema.sql +20 -0
- package/corpus/cases/09-write-grant-no-policy/case.json +24 -0
- package/corpus/cases/09-write-grant-no-policy/schema.sql +18 -0
- package/corpus/cases/10-rls-not-forced/case.json +24 -0
- package/corpus/cases/10-rls-not-forced/schema.sql +18 -0
- package/corpus/cases/11-session-user-in-policy/case.json +24 -0
- package/corpus/cases/11-session-user-in-policy/schema.sql +18 -0
- package/corpus/cases/12-volatile-function-in-policy/case.json +24 -0
- package/corpus/cases/12-volatile-function-in-policy/schema.sql +21 -0
- package/corpus/cases/13-security-definer-policy-wrapper/case.json +24 -0
- package/corpus/cases/13-security-definer-policy-wrapper/schema.sql +21 -0
- package/corpus/cases/14-dead-policy/case.json +24 -0
- package/corpus/cases/14-dead-policy/schema.sql +18 -0
- package/corpus/cases/15-unreachable-grant/case.json +24 -0
- package/corpus/cases/15-unreachable-grant/schema.sql +18 -0
- package/corpus/cases/16-anon-permissive-write-policy/case.json +34 -0
- package/corpus/cases/16-anon-permissive-write-policy/schema.sql +17 -0
- package/corpus/cases/17-foreign-key-without-index/case.json +24 -0
- package/corpus/cases/17-foreign-key-without-index/schema.sql +18 -0
- package/corpus/cases/18-policy-column-unindexed/case.json +24 -0
- package/corpus/cases/18-policy-column-unindexed/schema.sql +23 -0
- package/corpus/cases/19-policy-column-cast/case.json +24 -0
- package/corpus/cases/19-policy-column-cast/schema.sql +19 -0
- package/corpus/cases/20-stable-function-per-row/case.json +24 -0
- package/corpus/cases/20-stable-function-per-row/schema.sql +25 -0
- package/corpus/cases/21-table-without-primary-key/case.json +24 -0
- package/corpus/cases/21-table-without-primary-key/schema.sql +14 -0
- package/corpus/cases/22-redundant-index/case.json +24 -0
- package/corpus/cases/22-redundant-index/schema.sql +16 -0
- package/corpus/cases/23-tsvector-without-index/case.json +24 -0
- package/corpus/cases/23-tsvector-without-index/schema.sql +13 -0
- package/corpus/cases/24-anon-set-role-escalation/case.json +29 -0
- package/corpus/cases/24-anon-set-role-escalation/schema.sql +30 -0
- package/corpus/cases/25-definer-view-bypass/case.json +31 -0
- package/corpus/cases/25-definer-view-bypass/schema.sql +36 -0
- package/corpus/cases/26-invoker-view-no-bypass/case.json +31 -0
- package/corpus/cases/26-invoker-view-no-bypass/schema.sql +44 -0
- package/corpus/cases/27-definer-view-write/case.json +36 -0
- package/corpus/cases/27-definer-view-write/schema.sql +40 -0
- package/corpus/cases/28-invoker-view-no-write/case.json +31 -0
- package/corpus/cases/28-invoker-view-no-write/schema.sql +37 -0
- package/corpus/cases/29-rewrite-rule-bypass/case.json +36 -0
- package/corpus/cases/29-rewrite-rule-bypass/schema.sql +52 -0
- package/corpus/cases/30-instead-nothing-rule/case.json +31 -0
- package/corpus/cases/30-instead-nothing-rule/schema.sql +44 -0
- package/corpus/cases/31-matview-snapshot/case.json +32 -0
- package/corpus/cases/31-matview-snapshot/schema.sql +36 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/case.json +30 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/schema.sql +18 -0
- package/corpus/cases/33-leaky-filter-view/case.json +36 -0
- package/corpus/cases/33-leaky-filter-view/schema.sql +29 -0
- package/corpus/cases/34-barrier-view-no-leak/case.json +32 -0
- package/corpus/cases/34-barrier-view-no-leak/schema.sql +29 -0
- package/corpus/cases/35-column-grant-projection/case.json +27 -0
- package/corpus/cases/35-column-grant-projection/schema.sql +21 -0
- package/corpus/cases/35-policy-function-not-leakproof/case.json +24 -0
- package/corpus/cases/35-policy-function-not-leakproof/schema.sql +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/case.json +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/schema.sql +32 -0
- package/corpus/cases/36-policy-function-leakproof/case.json +24 -0
- package/corpus/cases/36-policy-function-leakproof/schema.sql +22 -0
- package/corpus/cases/37-sort-column-unindexed/case.json +24 -0
- package/corpus/cases/37-sort-column-unindexed/schema.sql +14 -0
- package/corpus/cases/37-unaudited-base-relation/case.json +30 -0
- package/corpus/cases/37-unaudited-base-relation/schema.sql +45 -0
- package/corpus/cases/38-foreign-key-with-index/case.json +22 -0
- package/corpus/cases/38-foreign-key-with-index/schema.sql +17 -0
- package/corpus/cases/38-invoker-view-unaudited-base/case.json +30 -0
- package/corpus/cases/38-invoker-view-unaudited-base/schema.sql +39 -0
- package/corpus/cases/39-definer-view-column-subset/case.json +30 -0
- package/corpus/cases/39-definer-view-column-subset/schema.sql +29 -0
- package/corpus/cases/39-policy-cast-with-expression-index/case.json +23 -0
- package/corpus/cases/39-policy-cast-with-expression-index/schema.sql +19 -0
- package/corpus/cases/40-definer-view-column-overreach/case.json +27 -0
- package/corpus/cases/40-definer-view-column-overreach/schema.sql +29 -0
- package/corpus/cases/40-stable-function-hoisted/case.json +23 -0
- package/corpus/cases/40-stable-function-hoisted/schema.sql +25 -0
- package/corpus/cases/41-index-prefix-not-redundant/case.json +21 -0
- package/corpus/cases/41-index-prefix-not-redundant/schema.sql +15 -0
- package/corpus/cases/41-unreadable-view-body/case.json +33 -0
- package/corpus/cases/41-unreadable-view-body/schema.sql +28 -0
- package/corpus/cases/42-invoker-view-unreadable-body/case.json +30 -0
- package/corpus/cases/42-invoker-view-unreadable-body/schema.sql +27 -0
- package/corpus/cases/42-search-column-indexed/case.json +21 -0
- package/corpus/cases/42-search-column-indexed/schema.sql +12 -0
- package/corpus/cases/43-anon-sequence-grant/case.json +27 -0
- package/corpus/cases/43-anon-sequence-grant/schema.sql +23 -0
- package/corpus/cases/44-serial-sequence-usage/case.json +27 -0
- package/corpus/cases/44-serial-sequence-usage/schema.sql +25 -0
- package/corpus/cases/45-anon-foreign-table/case.json +27 -0
- package/corpus/cases/45-anon-foreign-table/schema.sql +23 -0
- package/corpus/cases/46-unchecked-writable-view/case.json +41 -0
- package/corpus/cases/46-unchecked-writable-view/schema.sql +37 -0
- package/corpus/cases/47-checked-writable-view/case.json +37 -0
- package/corpus/cases/47-checked-writable-view/schema.sql +38 -0
- package/corpus/cases/48-definer-function-reach/case.json +38 -0
- package/corpus/cases/48-definer-function-reach/schema.sql +41 -0
- package/corpus/cases/49-invoker-function-no-reach/case.json +32 -0
- package/corpus/cases/49-invoker-function-no-reach/schema.sql +38 -0
- package/corpus/cases/50-instead-of-trigger-write/case.json +38 -0
- package/corpus/cases/50-instead-of-trigger-write/schema.sql +58 -0
- package/corpus/cases/51-invoker-trigger-no-write/case.json +33 -0
- package/corpus/cases/51-invoker-trigger-no-write/schema.sql +54 -0
- package/corpus/cases/52-revocable-execute-grant/case.json +30 -0
- package/corpus/cases/52-revocable-execute-grant/schema.sql +45 -0
- package/corpus/cases/53-policy-predicate-retains-execute/case.json +23 -0
- package/corpus/cases/53-policy-predicate-retains-execute/schema.sql +49 -0
- package/corpus/index.d.ts +79 -0
- package/corpus/index.js +125 -0
- package/esm/adapters.d.ts +8 -0
- package/esm/adapters.js +6 -0
- package/esm/ast/helpers.d.ts +16 -0
- package/esm/ast/helpers.js +36 -0
- package/esm/ast/parse.d.ts +31 -0
- package/esm/ast/parse.js +82 -0
- package/esm/ast/walk.d.ts +27 -0
- package/esm/ast/walk.js +72 -0
- package/esm/callgraph/baseline.d.ts +28 -0
- package/esm/callgraph/baseline.js +48 -0
- package/esm/callgraph/extract.d.ts +93 -0
- package/esm/callgraph/extract.js +333 -0
- package/esm/callgraph/graph.d.ts +82 -0
- package/esm/callgraph/graph.js +273 -0
- package/esm/checks/anti-patterns.d.ts +53 -0
- package/esm/checks/anti-patterns.js +240 -0
- package/esm/checks/column-grants.d.ts +20 -0
- package/esm/checks/column-grants.js +71 -0
- package/esm/checks/coverage.d.ts +43 -0
- package/esm/checks/coverage.js +104 -0
- package/esm/checks/definer-function.d.ts +91 -0
- package/esm/checks/definer-function.js +452 -0
- package/esm/checks/definer-view.d.ts +167 -0
- package/esm/checks/definer-view.js +382 -0
- package/esm/checks/indexes.d.ts +82 -0
- package/esm/checks/indexes.js +256 -0
- package/esm/checks/lattice.d.ts +179 -0
- package/esm/checks/lattice.js +475 -0
- package/esm/checks/object-acls.d.ts +32 -0
- package/esm/checks/object-acls.js +125 -0
- package/esm/checks/policy-index.d.ts +78 -0
- package/esm/checks/policy-index.js +342 -0
- package/esm/checks/revocable-grants.d.ts +140 -0
- package/esm/checks/revocable-grants.js +373 -0
- package/esm/checks/rls-flags.d.ts +25 -0
- package/esm/checks/rls-flags.js +77 -0
- package/esm/checks/role-reach.d.ts +329 -0
- package/esm/checks/role-reach.js +270 -0
- package/esm/checks/role-trust.d.ts +45 -0
- package/esm/checks/role-trust.js +86 -0
- package/esm/checks/set-role.d.ts +21 -0
- package/esm/checks/set-role.js +63 -0
- package/esm/checks/stats.d.ts +52 -0
- package/esm/checks/stats.js +184 -0
- package/esm/checks/view-exposure.d.ts +68 -0
- package/esm/checks/view-exposure.js +231 -0
- package/esm/checks/view-writes.d.ts +88 -0
- package/esm/checks/view-writes.js +344 -0
- package/esm/ci/baseline.d.ts +104 -0
- package/esm/ci/baseline.js +107 -0
- package/esm/ci/github.d.ts +47 -0
- package/esm/ci/github.js +161 -0
- package/esm/cli/audit.d.ts +3 -0
- package/esm/cli/audit.js +525 -0
- package/esm/cli/baseline.d.ts +3 -0
- package/esm/cli/baseline.js +102 -0
- package/esm/cli/commands.d.ts +2 -0
- package/esm/cli/commands.js +53 -0
- package/esm/cli/doctor.d.ts +3 -0
- package/esm/cli/doctor.js +67 -0
- package/esm/cli/eval.d.ts +3 -0
- package/esm/cli/eval.js +115 -0
- package/esm/cli/print-config.d.ts +3 -0
- package/esm/cli/print-config.js +52 -0
- package/esm/cli/shared.d.ts +43 -0
- package/esm/cli/shared.js +112 -0
- package/esm/cli.d.ts +2 -0
- package/esm/cli.js +67 -0
- package/esm/commands/audit.d.ts +65 -0
- package/esm/commands/audit.js +776 -0
- package/esm/commands/doctor.d.ts +19 -0
- package/esm/commands/doctor.js +178 -0
- package/esm/commands/eval.d.ts +67 -0
- package/esm/commands/eval.js +90 -0
- package/esm/config/fingerprint.d.ts +40 -0
- package/esm/config/fingerprint.js +68 -0
- package/esm/config/loader.d.ts +44 -0
- package/esm/config/loader.js +172 -0
- package/esm/config/presets.d.ts +95 -0
- package/esm/config/presets.js +324 -0
- package/esm/config/resolve.d.ts +47 -0
- package/esm/config/resolve.js +180 -0
- package/esm/config/schema.d.ts +54 -0
- package/esm/config/schema.js +265 -0
- package/esm/config/types.d.ts +562 -0
- package/esm/config/validate.d.ts +14 -0
- package/esm/config/validate.js +122 -0
- package/esm/corpus/index.d.ts +79 -0
- package/esm/corpus/index.js +85 -0
- package/esm/exposure/adapters.d.ts +152 -0
- package/esm/exposure/adapters.js +463 -0
- package/esm/exposure/planes.d.ts +59 -0
- package/esm/exposure/planes.js +144 -0
- package/esm/exposure/reach.d.ts +78 -0
- package/esm/exposure/reach.js +105 -0
- package/esm/exposure/routines.d.ts +36 -0
- package/esm/exposure/routines.js +54 -0
- package/esm/index.d.ts +96 -0
- package/esm/index.js +57 -6
- package/esm/lint/index.d.ts +11 -0
- package/esm/lint/index.js +10 -0
- package/esm/perf/baseline.d.ts +40 -0
- package/esm/perf/baseline.js +83 -0
- package/esm/perf/explain.d.ts +46 -0
- package/esm/perf/explain.js +201 -0
- package/esm/pg/acl.d.ts +72 -0
- package/esm/pg/acl.js +186 -0
- package/esm/pg/behaviors.d.ts +114 -0
- package/esm/pg/behaviors.js +206 -0
- package/esm/pg/exposure.d.ts +74 -0
- package/esm/pg/exposure.js +198 -0
- package/esm/pg/functions.d.ts +52 -0
- package/esm/pg/functions.js +128 -0
- package/esm/pg/indexes.d.ts +192 -0
- package/esm/pg/indexes.js +395 -0
- package/esm/pg/introspect.d.ts +134 -0
- package/esm/pg/introspect.js +274 -0
- package/esm/pg/objects.d.ts +37 -0
- package/esm/pg/objects.js +108 -0
- package/esm/pg/paths.d.ts +114 -0
- package/esm/pg/paths.js +159 -0
- package/esm/pg/proc.d.ts +23 -0
- package/esm/pg/proc.js +55 -0
- package/esm/pg/reach-inputs.d.ts +47 -0
- package/esm/pg/reach-inputs.js +133 -0
- package/esm/pg/roles.d.ts +28 -0
- package/esm/pg/roles.js +50 -0
- package/esm/pg/stats.d.ts +65 -0
- package/esm/pg/stats.js +129 -0
- package/esm/pgpm-test.d.ts +22 -0
- package/esm/pgpm-test.js +39 -0
- package/esm/report/callgraph.d.ts +9 -0
- package/esm/report/callgraph.js +74 -0
- package/esm/report/compare.d.ts +143 -0
- package/esm/report/compare.js +195 -0
- package/esm/report/github.d.ts +74 -0
- package/esm/report/github.js +290 -0
- package/esm/report/json.d.ts +4 -0
- package/esm/report/json.js +3 -0
- package/esm/report/markdown.d.ts +25 -0
- package/esm/report/markdown.js +352 -0
- package/esm/report/pretty.d.ts +14 -0
- package/esm/report/pretty.js +318 -0
- package/esm/report/sarif.d.ts +40 -0
- package/esm/report/sarif.js +189 -0
- package/esm/report/view.d.ts +82 -0
- package/esm/report/view.js +126 -0
- package/esm/rules/registry.d.ts +66 -0
- package/esm/rules/registry.js +562 -0
- package/esm/score/score.d.ts +84 -0
- package/esm/score/score.js +233 -0
- package/esm/score/scorecards.d.ts +68 -0
- package/esm/score/scorecards.js +157 -0
- package/esm/types.d.ts +292 -0
- package/esm/types.js +26 -0
- package/esm/version.d.ts +8 -0
- package/esm/version.js +9 -0
- package/exposure/adapters.d.ts +152 -0
- package/exposure/adapters.js +468 -0
- package/exposure/planes.d.ts +59 -0
- package/exposure/planes.js +151 -0
- package/exposure/reach.d.ts +78 -0
- package/exposure/reach.js +109 -0
- package/exposure/routines.d.ts +36 -0
- package/exposure/routines.js +57 -0
- package/index.d.ts +96 -6
- package/index.js +229 -6
- package/lint/index.d.ts +11 -0
- package/lint/index.js +19 -0
- package/package.json +54 -48
- package/perf/baseline.d.ts +40 -0
- package/perf/baseline.js +92 -0
- package/perf/explain.d.ts +46 -0
- package/perf/explain.js +204 -0
- package/pg/acl.d.ts +72 -0
- package/pg/acl.js +191 -0
- package/pg/behaviors.d.ts +114 -0
- package/pg/behaviors.js +217 -0
- package/pg/exposure.d.ts +74 -0
- package/pg/exposure.js +205 -0
- package/pg/functions.d.ts +52 -0
- package/pg/functions.js +131 -0
- package/pg/indexes.d.ts +192 -0
- package/pg/indexes.js +400 -0
- package/pg/introspect.d.ts +134 -0
- package/pg/introspect.js +280 -0
- package/pg/objects.d.ts +37 -0
- package/pg/objects.js +111 -0
- package/pg/paths.d.ts +114 -0
- package/pg/paths.js +166 -0
- package/pg/proc.d.ts +23 -0
- package/pg/proc.js +58 -0
- package/pg/reach-inputs.d.ts +47 -0
- package/pg/reach-inputs.js +136 -0
- package/pg/roles.d.ts +28 -0
- package/pg/roles.js +54 -0
- package/pg/stats.d.ts +65 -0
- package/pg/stats.js +133 -0
- package/pgpm-test.d.ts +22 -0
- package/pgpm-test.js +42 -0
- package/report/callgraph.d.ts +9 -0
- package/report/callgraph.js +81 -0
- package/report/compare.d.ts +143 -0
- package/report/compare.js +205 -0
- package/report/github.d.ts +74 -0
- package/report/github.js +333 -0
- package/report/json.d.ts +4 -0
- package/report/json.js +6 -0
- package/report/markdown.d.ts +25 -0
- package/report/markdown.js +355 -0
- package/report/pretty.d.ts +14 -0
- package/report/pretty.js +324 -0
- package/report/sarif.d.ts +40 -0
- package/report/sarif.js +193 -0
- package/report/view.d.ts +82 -0
- package/report/view.js +132 -0
- package/rules/registry.d.ts +66 -0
- package/rules/registry.js +569 -0
- package/schema/safegres.schema.json +1119 -0
- package/score/score.d.ts +84 -0
- package/score/score.js +238 -0
- package/score/scorecards.d.ts +68 -0
- package/score/scorecards.js +162 -0
- package/types.d.ts +292 -0
- package/types.js +32 -0
- package/version.d.ts +8 -0
- package/version.js +12 -0
- package/LICENSE +0 -23
- package/admin.d.ts +0 -26
- package/admin.js +0 -182
- package/connect.d.ts +0 -19
- package/connect.js +0 -83
- package/dist/README.md +0 -557
- package/dist/package.json +0 -72
- package/esm/admin.js +0 -178
- package/esm/connect.js +0 -78
- package/esm/manager.js +0 -136
- package/esm/roles.js +0 -32
- package/esm/seed/adapters.js +0 -23
- package/esm/seed/csv.js +0 -44
- package/esm/seed/index.js +0 -16
- package/esm/seed/json.js +0 -18
- package/esm/seed/launchql.js +0 -19
- package/esm/seed/sqitch.js +0 -17
- package/esm/stream.js +0 -43
- package/esm/test-client.js +0 -150
- package/manager.d.ts +0 -25
- package/manager.js +0 -140
- package/roles.d.ts +0 -17
- package/roles.js +0 -38
- package/seed/adapters.d.ts +0 -4
- package/seed/adapters.js +0 -28
- package/seed/csv.d.ts +0 -9
- package/seed/csv.js +0 -49
- package/seed/index.d.ts +0 -16
- package/seed/index.js +0 -33
- package/seed/json.d.ts +0 -6
- package/seed/json.js +0 -21
- package/seed/launchql.d.ts +0 -2
- package/seed/launchql.js +0 -22
- package/seed/sqitch.d.ts +0 -2
- package/seed/sqitch.js +0 -20
- package/seed/types.d.ts +0 -13
- package/stream.d.ts +0 -2
- package/stream.js +0 -46
- package/test-client.d.ts +0 -49
- package/test-client.js +0 -154
- /package/{seed → config}/types.js +0 -0
- /package/esm/{seed → config}/types.js +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog introspection for the performance dimension: indexes, foreign-key
|
|
3
|
+
* constraints, primary keys, and replica identity, in one query.
|
|
4
|
+
*
|
|
5
|
+
* Kept separate from {@link introspectTables} so a security-only audit never
|
|
6
|
+
* pays for it.
|
|
7
|
+
*/
|
|
8
|
+
import { type GrantInfo, type IntrospectOptions, type QueryExecutor } from './introspect';
|
|
9
|
+
export interface IndexInfo {
|
|
10
|
+
name: string;
|
|
11
|
+
/**
|
|
12
|
+
* Attribute numbers of the indexed columns, in index order. `0` marks an
|
|
13
|
+
* expression column (the expression itself is in `definition`).
|
|
14
|
+
*/
|
|
15
|
+
columns: number[];
|
|
16
|
+
/** Column names aligned with `columns`; expression columns render as `(expr)`. */
|
|
17
|
+
columnNames: string[];
|
|
18
|
+
unique: boolean;
|
|
19
|
+
primary: boolean;
|
|
20
|
+
/** Backed by a constraint (PRIMARY KEY / UNIQUE / EXCLUDE). */
|
|
21
|
+
constraint: boolean;
|
|
22
|
+
/** Has a `WHERE` clause — only covers a subset of rows. */
|
|
23
|
+
partial: boolean;
|
|
24
|
+
/** Contains at least one expression column. */
|
|
25
|
+
expression: boolean;
|
|
26
|
+
/** Access method (`btree`, `gin`, …). */
|
|
27
|
+
method: string;
|
|
28
|
+
definition: string;
|
|
29
|
+
/**
|
|
30
|
+
* A partition of an index on the parent partitioned table
|
|
31
|
+
* (`pg_class.relispartition` on the index). Such an index cannot be dropped
|
|
32
|
+
* on its own; it exists because the parent's index does.
|
|
33
|
+
*/
|
|
34
|
+
attached: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ColumnInfo {
|
|
37
|
+
name: string;
|
|
38
|
+
/** `pg_attribute.attnum`. */
|
|
39
|
+
attnum: number;
|
|
40
|
+
/** `format_type()` output, e.g. `timestamptz`, `tsvector`, `vector(1536)`. */
|
|
41
|
+
type: string;
|
|
42
|
+
/** Base type name without modifiers, e.g. `timestamptz`, `vector`. */
|
|
43
|
+
baseType: string;
|
|
44
|
+
/** `pg_attribute.attnotnull`. */
|
|
45
|
+
notNull: boolean;
|
|
46
|
+
/** The column default as SQL, or `null` when it has none. */
|
|
47
|
+
defaultExpr: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface ForeignKeyInfo {
|
|
50
|
+
name: string;
|
|
51
|
+
/** Referencing column attribute numbers, in constraint order. */
|
|
52
|
+
columns: number[];
|
|
53
|
+
columnNames: string[];
|
|
54
|
+
/** Schema-qualified referenced table. */
|
|
55
|
+
references: string;
|
|
56
|
+
}
|
|
57
|
+
export interface TableIndexSnapshot {
|
|
58
|
+
schema: string;
|
|
59
|
+
name: string;
|
|
60
|
+
oid: number;
|
|
61
|
+
/** The table is a partition of a partitioned table (indexes live on the parent). */
|
|
62
|
+
isPartition: boolean;
|
|
63
|
+
/** `pg_class.relreplident`: `d` default, `n` nothing, `f` full, `i` index. */
|
|
64
|
+
replicaIdentity: string;
|
|
65
|
+
hasPrimaryKey: boolean;
|
|
66
|
+
/** `pg_class.reltuples` — planner row estimate, `-1` when never analyzed. */
|
|
67
|
+
estimatedRows: number;
|
|
68
|
+
/** User columns (no system or dropped attributes), in attribute order. */
|
|
69
|
+
columns: ColumnInfo[];
|
|
70
|
+
indexes: IndexInfo[];
|
|
71
|
+
foreignKeys: ForeignKeyInfo[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* One-query snapshot of the index-shaped catalog for every table in scope.
|
|
75
|
+
*/
|
|
76
|
+
export declare function introspectIndexes(exec: QueryExecutor, options?: Pick<IntrospectOptions, 'schemas' | 'excludeSchemas' | 'extensions'>): Promise<TableIndexSnapshot[]>;
|
|
77
|
+
/** A view (or materialized view) with everything an access check needs. */
|
|
78
|
+
export interface ViewSnapshot {
|
|
79
|
+
schema: string;
|
|
80
|
+
name: string;
|
|
81
|
+
/**
|
|
82
|
+
* The role a non-`security_invoker` view executes as. Postgres checks the
|
|
83
|
+
* base relations' ACLs and policies against *this* role, not the caller's.
|
|
84
|
+
*/
|
|
85
|
+
owner: string;
|
|
86
|
+
/** `relkind = 'm'` — stored rows, so a query never touches the bases. */
|
|
87
|
+
materialized: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* `reloptions.security_invoker`. When true the view executes as the caller
|
|
90
|
+
* and confers nothing; when false (the default) it executes as `owner`.
|
|
91
|
+
*/
|
|
92
|
+
securityInvoker: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* `reloptions.security_barrier`. Without it the planner may push a caller's
|
|
95
|
+
* own qual below the view's `WHERE`, so a leaky operator or function sees
|
|
96
|
+
* the rows the view filters out — the view is a filter, not a boundary.
|
|
97
|
+
*/
|
|
98
|
+
securityBarrier: boolean;
|
|
99
|
+
/** The owner is a superuser or has BYPASSRLS: the bases' policies never run. */
|
|
100
|
+
ownerBypassesRls: boolean;
|
|
101
|
+
/** ACL rows on the view itself, in the same shape as a table's. */
|
|
102
|
+
grants: GrantInfo[];
|
|
103
|
+
/** `pg_get_viewdef()` — the body, as SQL text. */
|
|
104
|
+
definition: string;
|
|
105
|
+
/**
|
|
106
|
+
* The columns of each relation the view body actually reads, from the
|
|
107
|
+
* `pg_depend` rows on the view's `_RETURN` rule. The catalog answers this
|
|
108
|
+
* exactly — `SELECT *` is expanded at CREATE time, expressions and joins
|
|
109
|
+
* are accounted for, and a nested view depends on the *inner view's*
|
|
110
|
+
* columns, so the list is per hop. Absent when the caller built the
|
|
111
|
+
* snapshot without it.
|
|
112
|
+
*/
|
|
113
|
+
columnDeps?: Array<{
|
|
114
|
+
schema: string;
|
|
115
|
+
table: string;
|
|
116
|
+
columns: string[];
|
|
117
|
+
}>;
|
|
118
|
+
/**
|
|
119
|
+
* The write commands Postgres accepts on the view, from
|
|
120
|
+
* `pg_relation_is_updatable`. A simple view is *auto-updatable*: the write
|
|
121
|
+
* is rewritten onto its single base relation, and on a definer view that
|
|
122
|
+
* rewrite runs with the owner's privileges — a write edge the body alone
|
|
123
|
+
* cannot prove, which is why it is read from the catalog.
|
|
124
|
+
*
|
|
125
|
+
* The bitmask also counts updatability conferred by rules and `INSTEAD OF`
|
|
126
|
+
* triggers, so it is only auto-updatability when {@link rules} is empty and
|
|
127
|
+
* {@link insteadOfTriggers} is false.
|
|
128
|
+
*/
|
|
129
|
+
writable: Array<'INSERT' | 'UPDATE' | 'DELETE'>;
|
|
130
|
+
/** The view has `INSTEAD OF` triggers: writes go wherever their bodies say. */
|
|
131
|
+
insteadOfTriggers: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Those triggers, with the function each one runs. A write against the view
|
|
134
|
+
* becomes that function's body, and the body is permission-checked against
|
|
135
|
+
* the *function's* effective user — the caller, unless the function is
|
|
136
|
+
* SECURITY DEFINER. The view's own owner does not enter into it, which is
|
|
137
|
+
* why the trigger function has to be named rather than assumed.
|
|
138
|
+
*/
|
|
139
|
+
insteadOf: InsteadOfTrigger[];
|
|
140
|
+
/**
|
|
141
|
+
* `WITH [LOCAL | CASCADED] CHECK OPTION`, from `reloptions.check_option`.
|
|
142
|
+
* `'none'` — the default — means the view's own `WHERE` constrains which
|
|
143
|
+
* rows come *out* and nothing about which rows go *in*: a writer can insert
|
|
144
|
+
* or update rows the view will not then show them, straight past the filter
|
|
145
|
+
* the view exists to apply. `'local'` enforces this view's condition,
|
|
146
|
+
* `'cascaded'` also enforces every underlying view's.
|
|
147
|
+
*/
|
|
148
|
+
checkOption: 'none' | 'local' | 'cascaded';
|
|
149
|
+
/**
|
|
150
|
+
* Rewrite rules other than the view's own `_RETURN` SELECT rule. These are
|
|
151
|
+
* invisible to `pg_get_viewdef`, and their actions are permission-checked
|
|
152
|
+
* against the *rule's table owner* — the view owner — regardless of
|
|
153
|
+
* `security_invoker`, which only governs the view's own base relations.
|
|
154
|
+
*/
|
|
155
|
+
rules: ViewRule[];
|
|
156
|
+
}
|
|
157
|
+
/** An `INSTEAD OF` trigger on a view, and the function it fires. */
|
|
158
|
+
export interface InsteadOfTrigger {
|
|
159
|
+
name: string;
|
|
160
|
+
/** The commands on the view that fire it. */
|
|
161
|
+
events: Array<'INSERT' | 'UPDATE' | 'DELETE'>;
|
|
162
|
+
functionSchema: string;
|
|
163
|
+
functionName: string;
|
|
164
|
+
}
|
|
165
|
+
/** A rewrite rule on a view, other than the `_RETURN` rule that defines it. */
|
|
166
|
+
export interface ViewRule {
|
|
167
|
+
name: string;
|
|
168
|
+
/** The command on the view that fires the rule. */
|
|
169
|
+
event: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
170
|
+
/** `DO INSTEAD` — the original command is replaced by the rule's actions. */
|
|
171
|
+
instead: boolean;
|
|
172
|
+
/** `pg_get_ruledef()` — the whole `CREATE RULE`, actions included. */
|
|
173
|
+
definition: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Every view and materialized view in scope, with its owner, its
|
|
177
|
+
* `security_invoker` setting and its own ACL.
|
|
178
|
+
*
|
|
179
|
+
* The definition alone answers "is this column read by anything" (see
|
|
180
|
+
* {@link classifyPaths}); the owner and `security_invoker` answer *whose*
|
|
181
|
+
* privileges the read runs under, which is what the view-ownership reach edge
|
|
182
|
+
* needs (L8).
|
|
183
|
+
*/
|
|
184
|
+
export declare function introspectViews(exec: QueryExecutor, options?: Pick<IntrospectOptions, 'schemas' | 'excludeSchemas'>): Promise<ViewSnapshot[]>;
|
|
185
|
+
/**
|
|
186
|
+
* Every view and materialized-view body in scope, as SQL text.
|
|
187
|
+
*
|
|
188
|
+
* A column named in a view definition is read by something, which is the
|
|
189
|
+
* cheapest available refutation of "nothing queries this column" — see
|
|
190
|
+
* {@link classifyPaths}.
|
|
191
|
+
*/
|
|
192
|
+
export declare function introspectViewBodies(exec: QueryExecutor, options?: Pick<IntrospectOptions, 'schemas' | 'excludeSchemas'>): Promise<string[]>;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog introspection for the performance dimension: indexes, foreign-key
|
|
3
|
+
* constraints, primary keys, and replica identity, in one query.
|
|
4
|
+
*
|
|
5
|
+
* Kept separate from {@link introspectTables} so a security-only audit never
|
|
6
|
+
* pays for it.
|
|
7
|
+
*/
|
|
8
|
+
import { extensionFilter } from './introspect';
|
|
9
|
+
const DEFAULT_EXCLUDES = ['pg_catalog', 'information_schema', 'pg_toast'];
|
|
10
|
+
/**
|
|
11
|
+
* One-query snapshot of the index-shaped catalog for every table in scope.
|
|
12
|
+
*/
|
|
13
|
+
export async function introspectIndexes(exec, options = {}) {
|
|
14
|
+
const excludes = [...DEFAULT_EXCLUDES, ...(options.excludeSchemas ?? [])];
|
|
15
|
+
const schemaFilter = options.schemas && options.schemas.length > 0
|
|
16
|
+
? `AND n.nspname = ANY($1::text[])`
|
|
17
|
+
: `AND NOT (n.nspname = ANY($2::text[]))`;
|
|
18
|
+
const extFilter = extensionFilter(options.extensions, 3);
|
|
19
|
+
// Every parameter is referenced (even when only one filters) so Postgres
|
|
20
|
+
// can infer their types — an unused $N errors out at bind time.
|
|
21
|
+
const sql = `
|
|
22
|
+
WITH _params AS (
|
|
23
|
+
SELECT
|
|
24
|
+
$1::text[] AS include_schemas,
|
|
25
|
+
$2::text[] AS exclude_schemas,
|
|
26
|
+
$3::text[] AS ignore_extensions
|
|
27
|
+
),
|
|
28
|
+
rels AS (
|
|
29
|
+
SELECT
|
|
30
|
+
n.nspname AS schema_name,
|
|
31
|
+
c.relname AS table_name,
|
|
32
|
+
c.oid AS oid,
|
|
33
|
+
c.relispartition AS is_partition,
|
|
34
|
+
c.relreplident AS replica_identity,
|
|
35
|
+
c.reltuples AS estimated_rows
|
|
36
|
+
FROM pg_class c
|
|
37
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
38
|
+
WHERE c.relkind IN ('r', 'p')
|
|
39
|
+
${schemaFilter}${extFilter}
|
|
40
|
+
),
|
|
41
|
+
indexes AS (
|
|
42
|
+
SELECT
|
|
43
|
+
ix.indrelid AS oid,
|
|
44
|
+
i.relname AS name,
|
|
45
|
+
string_to_array(ix.indkey::text, ' ')::int[] AS columns,
|
|
46
|
+
ix.indisunique AS is_unique,
|
|
47
|
+
ix.indisprimary AS is_primary,
|
|
48
|
+
(con.oid IS NOT NULL) AS is_constraint,
|
|
49
|
+
(ix.indpred IS NOT NULL) AS is_partial,
|
|
50
|
+
(ix.indexprs IS NOT NULL) AS is_expression,
|
|
51
|
+
am.amname AS method,
|
|
52
|
+
pg_get_indexdef(i.oid) AS definition,
|
|
53
|
+
i.relispartition AS is_attached
|
|
54
|
+
FROM pg_index ix
|
|
55
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
56
|
+
JOIN pg_am am ON am.oid = i.relam
|
|
57
|
+
LEFT JOIN pg_constraint con
|
|
58
|
+
ON con.conindid = i.oid AND con.contype IN ('p', 'u', 'x')
|
|
59
|
+
WHERE ix.indislive
|
|
60
|
+
),
|
|
61
|
+
fks AS (
|
|
62
|
+
SELECT
|
|
63
|
+
co.conrelid AS oid,
|
|
64
|
+
co.conname AS name,
|
|
65
|
+
co.conkey::int[] AS columns,
|
|
66
|
+
co.confrelid::regclass::text AS references_table
|
|
67
|
+
FROM pg_constraint co
|
|
68
|
+
WHERE co.contype = 'f'
|
|
69
|
+
)
|
|
70
|
+
SELECT
|
|
71
|
+
r.schema_name,
|
|
72
|
+
r.table_name,
|
|
73
|
+
r.oid::int AS oid,
|
|
74
|
+
r.is_partition,
|
|
75
|
+
r.replica_identity::text AS replica_identity,
|
|
76
|
+
r.estimated_rows::float8 AS estimated_rows,
|
|
77
|
+
COALESCE(
|
|
78
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
79
|
+
'name', x.name,
|
|
80
|
+
'columns', to_jsonb(x.columns),
|
|
81
|
+
'columnNames', to_jsonb(${columnNamesExpr('x')}),
|
|
82
|
+
'unique', x.is_unique,
|
|
83
|
+
'primary', x.is_primary,
|
|
84
|
+
'constraint', x.is_constraint,
|
|
85
|
+
'partial', x.is_partial,
|
|
86
|
+
'expression', x.is_expression,
|
|
87
|
+
'method', x.method,
|
|
88
|
+
'definition', x.definition,
|
|
89
|
+
'attached', x.is_attached
|
|
90
|
+
) ORDER BY x.name) FROM indexes x WHERE x.oid = r.oid),
|
|
91
|
+
'[]'::jsonb
|
|
92
|
+
) AS indexes,
|
|
93
|
+
COALESCE(
|
|
94
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
95
|
+
'name', f.name,
|
|
96
|
+
'columns', to_jsonb(f.columns),
|
|
97
|
+
'columnNames', to_jsonb(${columnNamesExpr('f')}),
|
|
98
|
+
'references', f.references_table
|
|
99
|
+
) ORDER BY f.name) FROM fks f WHERE f.oid = r.oid),
|
|
100
|
+
'[]'::jsonb
|
|
101
|
+
) AS foreign_keys,
|
|
102
|
+
COALESCE(
|
|
103
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
104
|
+
'name', a.attname,
|
|
105
|
+
'attnum', a.attnum,
|
|
106
|
+
'type', format_type(a.atttypid, a.atttypmod),
|
|
107
|
+
'baseType', t.typname,
|
|
108
|
+
'notNull', a.attnotnull,
|
|
109
|
+
'defaultExpr', pg_get_expr(d.adbin, d.adrelid)
|
|
110
|
+
) ORDER BY a.attnum)
|
|
111
|
+
FROM pg_attribute a
|
|
112
|
+
JOIN pg_type t ON t.oid = a.atttypid
|
|
113
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
114
|
+
WHERE a.attrelid = r.oid AND a.attnum > 0 AND NOT a.attisdropped),
|
|
115
|
+
'[]'::jsonb
|
|
116
|
+
) AS columns
|
|
117
|
+
FROM rels r
|
|
118
|
+
ORDER BY r.schema_name, r.table_name
|
|
119
|
+
`;
|
|
120
|
+
const { rows } = await exec.query(sql, [options.schemas ?? [], excludes, options.extensions?.ignore ?? []]);
|
|
121
|
+
return rows.map((r) => ({
|
|
122
|
+
schema: r.schema_name,
|
|
123
|
+
name: r.table_name,
|
|
124
|
+
oid: r.oid,
|
|
125
|
+
isPartition: r.is_partition,
|
|
126
|
+
replicaIdentity: r.replica_identity,
|
|
127
|
+
hasPrimaryKey: r.indexes.some((i) => i.primary),
|
|
128
|
+
estimatedRows: Number(r.estimated_rows),
|
|
129
|
+
columns: r.columns,
|
|
130
|
+
indexes: r.indexes,
|
|
131
|
+
foreignKeys: r.foreign_keys
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
/** `pg_rewrite.ev_type` is a char code, not the command name. */
|
|
135
|
+
const RULE_EVENTS = {
|
|
136
|
+
1: 'SELECT',
|
|
137
|
+
2: 'UPDATE',
|
|
138
|
+
3: 'INSERT',
|
|
139
|
+
4: 'DELETE'
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Every view and materialized view in scope, with its owner, its
|
|
143
|
+
* `security_invoker` setting and its own ACL.
|
|
144
|
+
*
|
|
145
|
+
* The definition alone answers "is this column read by anything" (see
|
|
146
|
+
* {@link classifyPaths}); the owner and `security_invoker` answer *whose*
|
|
147
|
+
* privileges the read runs under, which is what the view-ownership reach edge
|
|
148
|
+
* needs (L8).
|
|
149
|
+
*/
|
|
150
|
+
export async function introspectViews(exec, options = {}) {
|
|
151
|
+
const excludes = [...DEFAULT_EXCLUDES, ...(options.excludeSchemas ?? [])];
|
|
152
|
+
const schemaFilter = options.schemas && options.schemas.length > 0
|
|
153
|
+
? `AND n.nspname = ANY($1::text[])`
|
|
154
|
+
: `AND NOT (n.nspname = ANY($2::text[]))`;
|
|
155
|
+
const { rows } = await exec.query(
|
|
156
|
+
// Both parameters are referenced (even when only one filters) so Postgres
|
|
157
|
+
// can infer their types — an unused $N errors out at bind time.
|
|
158
|
+
`WITH _params AS (
|
|
159
|
+
SELECT $1::text[] AS include_schemas, $2::text[] AS exclude_schemas
|
|
160
|
+
),
|
|
161
|
+
views AS (
|
|
162
|
+
SELECT
|
|
163
|
+
n.nspname AS schema_name,
|
|
164
|
+
c.relname AS view_name,
|
|
165
|
+
c.oid AS oid,
|
|
166
|
+
c.relkind = 'm' AS materialized,
|
|
167
|
+
pg_catalog.pg_get_userbyid(c.relowner) AS owner,
|
|
168
|
+
COALESCE(o.rolsuper OR o.rolbypassrls, false) AS owner_bypasses_rls,
|
|
169
|
+
-- Postgres stores a boolean reloption verbatim, so this is any of
|
|
170
|
+
-- true/on/1/yes/t/y depending on how the view was written; the cast
|
|
171
|
+
-- accepts every spelling the option itself accepts, and reloption
|
|
172
|
+
-- validation at CREATE time guarantees it parses.
|
|
173
|
+
COALESCE(
|
|
174
|
+
(SELECT option_value FROM pg_options_to_table(c.reloptions)
|
|
175
|
+
WHERE option_name = 'security_invoker'),
|
|
176
|
+
'false'
|
|
177
|
+
)::boolean AS security_invoker,
|
|
178
|
+
-- Same spelling latitude, and the same reason to read it: a view
|
|
179
|
+
-- used as a row filter is only a boundary when it is a barrier.
|
|
180
|
+
COALESCE(
|
|
181
|
+
(SELECT option_value FROM pg_options_to_table(c.reloptions)
|
|
182
|
+
WHERE option_name = 'security_barrier'),
|
|
183
|
+
'false'
|
|
184
|
+
)::boolean AS security_barrier,
|
|
185
|
+
c.relacl AS relacl,
|
|
186
|
+
pg_get_viewdef(c.oid) AS definition,
|
|
187
|
+
-- Bitmask over 1 << CMD_*: UPDATE 4, INSERT 8, DELETE 16. The second
|
|
188
|
+
-- argument asks the same question the rewriter asks at runtime, so
|
|
189
|
+
-- rules and INSTEAD OF triggers count towards it too.
|
|
190
|
+
pg_relation_is_updatable(c.oid, true) AS updatable_bits,
|
|
191
|
+
-- 'local' | 'cascaded' when the view was created WITH CHECK OPTION,
|
|
192
|
+
-- absent otherwise. Unlike the boolean reloptions above this one is
|
|
193
|
+
-- already a keyword, so it needs no cast.
|
|
194
|
+
COALESCE(
|
|
195
|
+
(SELECT option_value FROM pg_options_to_table(c.reloptions)
|
|
196
|
+
WHERE option_name = 'check_option'),
|
|
197
|
+
'none'
|
|
198
|
+
) AS check_option,
|
|
199
|
+
-- TRIGGER_TYPE_INSTEAD = 1 << 6.
|
|
200
|
+
EXISTS (
|
|
201
|
+
SELECT 1 FROM pg_trigger t
|
|
202
|
+
WHERE t.tgrelid = c.oid AND NOT t.tgisinternal AND (t.tgtype & 64) <> 0
|
|
203
|
+
) AS instead_of_triggers
|
|
204
|
+
FROM pg_class c
|
|
205
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
206
|
+
LEFT JOIN pg_roles o ON o.oid = c.relowner
|
|
207
|
+
WHERE c.relkind IN ('v', 'm')
|
|
208
|
+
${schemaFilter}
|
|
209
|
+
),
|
|
210
|
+
grants AS (
|
|
211
|
+
SELECT
|
|
212
|
+
v.oid,
|
|
213
|
+
CASE WHEN a.grantee = 0 THEN 'PUBLIC' ELSE rol.rolname END AS grantee,
|
|
214
|
+
a.privilege_type,
|
|
215
|
+
a.is_grantable,
|
|
216
|
+
CASE
|
|
217
|
+
WHEN a.grantee = 0 THEN false
|
|
218
|
+
ELSE COALESCE(rol.rolsuper OR rol.rolbypassrls, false)
|
|
219
|
+
END AS bypass_rls
|
|
220
|
+
FROM views v, aclexplode(v.relacl) a
|
|
221
|
+
LEFT JOIN pg_roles rol ON rol.oid = a.grantee
|
|
222
|
+
WHERE v.relacl IS NOT NULL
|
|
223
|
+
),
|
|
224
|
+
-- Which columns of which relation the view body reads. The rewriter
|
|
225
|
+
-- records one pg_depend row per referenced column, so this is the
|
|
226
|
+
-- catalog's own answer to "what escapes through this view" — no parsing,
|
|
227
|
+
-- no star expansion, no alias resolution.
|
|
228
|
+
column_deps AS (
|
|
229
|
+
SELECT DISTINCT
|
|
230
|
+
r.ev_class AS view_oid,
|
|
231
|
+
dn.nspname AS dep_schema,
|
|
232
|
+
dc.relname AS dep_table,
|
|
233
|
+
a.attname AS dep_column
|
|
234
|
+
FROM pg_depend d
|
|
235
|
+
JOIN pg_rewrite r ON r.oid = d.objid AND d.classid = 'pg_rewrite'::regclass
|
|
236
|
+
JOIN views v ON v.oid = r.ev_class
|
|
237
|
+
JOIN pg_class dc ON dc.oid = d.refobjid
|
|
238
|
+
JOIN pg_namespace dn ON dn.oid = dc.relnamespace
|
|
239
|
+
JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
|
|
240
|
+
WHERE d.refclassid = 'pg_class'::regclass
|
|
241
|
+
AND d.refobjsubid > 0
|
|
242
|
+
AND r.ev_class <> d.refobjid
|
|
243
|
+
),
|
|
244
|
+
instead_of AS (
|
|
245
|
+
-- The same TRIGGER_TYPE_INSTEAD bit as above, but carrying the function:
|
|
246
|
+
-- where a write against the view actually goes is in that body.
|
|
247
|
+
SELECT
|
|
248
|
+
t.tgrelid AS oid,
|
|
249
|
+
t.tgname,
|
|
250
|
+
t.tgtype,
|
|
251
|
+
pn.nspname AS fn_schema,
|
|
252
|
+
p.proname AS fn_name
|
|
253
|
+
FROM pg_trigger t
|
|
254
|
+
JOIN views v ON v.oid = t.tgrelid
|
|
255
|
+
JOIN pg_proc p ON p.oid = t.tgfoid
|
|
256
|
+
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
|
257
|
+
WHERE NOT t.tgisinternal AND (t.tgtype & 64) <> 0
|
|
258
|
+
),
|
|
259
|
+
rules AS (
|
|
260
|
+
-- _RETURN is the SELECT rule that *is* the view; every other rule is
|
|
261
|
+
-- behaviour pg_get_viewdef does not show.
|
|
262
|
+
SELECT
|
|
263
|
+
r.ev_class AS oid,
|
|
264
|
+
r.rulename,
|
|
265
|
+
r.ev_type,
|
|
266
|
+
r.is_instead,
|
|
267
|
+
pg_get_ruledef(r.oid) AS definition
|
|
268
|
+
FROM pg_rewrite r
|
|
269
|
+
JOIN views v ON v.oid = r.ev_class
|
|
270
|
+
WHERE r.rulename <> '_RETURN'
|
|
271
|
+
)
|
|
272
|
+
SELECT
|
|
273
|
+
v.schema_name,
|
|
274
|
+
v.view_name,
|
|
275
|
+
v.owner,
|
|
276
|
+
v.materialized,
|
|
277
|
+
v.security_invoker,
|
|
278
|
+
v.security_barrier,
|
|
279
|
+
v.owner_bypasses_rls,
|
|
280
|
+
v.definition,
|
|
281
|
+
COALESCE(
|
|
282
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
283
|
+
'role', g.grantee,
|
|
284
|
+
'privilege', g.privilege_type,
|
|
285
|
+
'grantable', g.is_grantable,
|
|
286
|
+
'bypassRls', g.bypass_rls
|
|
287
|
+
)) FROM grants g WHERE g.oid = v.oid),
|
|
288
|
+
'[]'::jsonb
|
|
289
|
+
) AS grants,
|
|
290
|
+
COALESCE(
|
|
291
|
+
(SELECT jsonb_agg(t) FROM (
|
|
292
|
+
SELECT jsonb_build_object(
|
|
293
|
+
'schema', cd.dep_schema,
|
|
294
|
+
'table', cd.dep_table,
|
|
295
|
+
'columns', jsonb_agg(cd.dep_column ORDER BY cd.dep_column)
|
|
296
|
+
) AS t
|
|
297
|
+
FROM column_deps cd
|
|
298
|
+
WHERE cd.view_oid = v.oid
|
|
299
|
+
GROUP BY cd.dep_schema, cd.dep_table
|
|
300
|
+
) deps),
|
|
301
|
+
'[]'::jsonb
|
|
302
|
+
) AS column_deps,
|
|
303
|
+
v.updatable_bits,
|
|
304
|
+
v.check_option,
|
|
305
|
+
v.instead_of_triggers,
|
|
306
|
+
COALESCE(
|
|
307
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
308
|
+
'name', i.tgname,
|
|
309
|
+
'tgtype', i.tgtype,
|
|
310
|
+
'fnSchema', i.fn_schema,
|
|
311
|
+
'fnName', i.fn_name
|
|
312
|
+
)) FROM instead_of i WHERE i.oid = v.oid),
|
|
313
|
+
'[]'::jsonb
|
|
314
|
+
) AS instead_of,
|
|
315
|
+
COALESCE(
|
|
316
|
+
(SELECT jsonb_agg(jsonb_build_object(
|
|
317
|
+
'name', r.rulename,
|
|
318
|
+
'event', r.ev_type,
|
|
319
|
+
'instead', r.is_instead,
|
|
320
|
+
'definition', r.definition
|
|
321
|
+
)) FROM rules r WHERE r.oid = v.oid),
|
|
322
|
+
'[]'::jsonb
|
|
323
|
+
) AS rules
|
|
324
|
+
FROM views v
|
|
325
|
+
ORDER BY v.schema_name, v.view_name`, [options.schemas ?? [], excludes]);
|
|
326
|
+
return rows.map((r) => ({
|
|
327
|
+
schema: r.schema_name,
|
|
328
|
+
name: r.view_name,
|
|
329
|
+
owner: r.owner,
|
|
330
|
+
materialized: r.materialized,
|
|
331
|
+
securityInvoker: r.security_invoker,
|
|
332
|
+
securityBarrier: r.security_barrier,
|
|
333
|
+
ownerBypassesRls: r.owner_bypasses_rls,
|
|
334
|
+
grants: r.grants.map((g) => ({
|
|
335
|
+
role: g.role,
|
|
336
|
+
privilege: g.privilege,
|
|
337
|
+
grantable: g.grantable,
|
|
338
|
+
bypassRls: g.bypassRls
|
|
339
|
+
})),
|
|
340
|
+
definition: r.definition,
|
|
341
|
+
columnDeps: r.column_deps,
|
|
342
|
+
writable: [
|
|
343
|
+
...(r.updatable_bits & 8 ? ['INSERT'] : []),
|
|
344
|
+
...(r.updatable_bits & 4 ? ['UPDATE'] : []),
|
|
345
|
+
...(r.updatable_bits & 16 ? ['DELETE'] : [])
|
|
346
|
+
],
|
|
347
|
+
checkOption: r.check_option === 'local' || r.check_option === 'cascaded'
|
|
348
|
+
? r.check_option
|
|
349
|
+
: 'none',
|
|
350
|
+
insteadOfTriggers: r.instead_of_triggers,
|
|
351
|
+
insteadOf: r.instead_of.map((t) => ({
|
|
352
|
+
name: t.name,
|
|
353
|
+
// tgtype bits: INSERT 1 << 2, DELETE 1 << 3, UPDATE 1 << 4.
|
|
354
|
+
events: [
|
|
355
|
+
...(t.tgtype & 4 ? ['INSERT'] : []),
|
|
356
|
+
...(t.tgtype & 16 ? ['UPDATE'] : []),
|
|
357
|
+
...(t.tgtype & 8 ? ['DELETE'] : [])
|
|
358
|
+
],
|
|
359
|
+
functionSchema: t.fnSchema,
|
|
360
|
+
functionName: t.fnName
|
|
361
|
+
})),
|
|
362
|
+
rules: r.rules.flatMap((rule) => {
|
|
363
|
+
const event = RULE_EVENTS[rule.event];
|
|
364
|
+
return event
|
|
365
|
+
? [{ name: rule.name, event, instead: rule.instead, definition: rule.definition }]
|
|
366
|
+
: [];
|
|
367
|
+
})
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Every view and materialized-view body in scope, as SQL text.
|
|
372
|
+
*
|
|
373
|
+
* A column named in a view definition is read by something, which is the
|
|
374
|
+
* cheapest available refutation of "nothing queries this column" — see
|
|
375
|
+
* {@link classifyPaths}.
|
|
376
|
+
*/
|
|
377
|
+
export async function introspectViewBodies(exec, options = {}) {
|
|
378
|
+
return (await introspectViews(exec, options)).map((v) => v.definition);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Resolve attribute numbers to column names for the relation `alias.oid`.
|
|
382
|
+
* `0` (an expression column) renders as `(expr)` so positions stay aligned.
|
|
383
|
+
*/
|
|
384
|
+
function columnNamesExpr(alias) {
|
|
385
|
+
return `(
|
|
386
|
+
SELECT COALESCE(array_agg(
|
|
387
|
+
COALESCE((
|
|
388
|
+
SELECT att.attname
|
|
389
|
+
FROM pg_attribute att
|
|
390
|
+
WHERE att.attrelid = ${alias}.oid AND att.attnum = k.attnum
|
|
391
|
+
), '(expr)') ORDER BY k.ord
|
|
392
|
+
), ARRAY[]::text[])
|
|
393
|
+
FROM unnest(${alias}.columns) WITH ORDINALITY AS k(attnum, ord)
|
|
394
|
+
)`;
|
|
395
|
+
}
|