@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[]>;
@@ -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
+ }