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