@shortlink-org/portolan 0.1.0 → 0.2.0

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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -1,893 +0,0 @@
1
- package main
2
-
3
- import (
4
- "fmt"
5
- "strings"
6
-
7
- "github.com/pgplex/pgparser/nodes"
8
- "github.com/pgplex/pgparser/parser"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- )
12
-
13
- // Reading DDL.
14
- //
15
- // The parse tree comes from pgplex/pgparser, which is PostgreSQL's own grammar
16
- // ported to Go: no cgo, no database, no external binary. That matters more than
17
- // it sounds. Every other extractor here runs on a bare checkout, and the
18
- // alternative - replaying the migrations into a dev database to be told what
19
- // they built - would have made generating a catalog need Docker.
20
- //
21
- // What is read is deliberately a small part of what the grammar accepts:
22
- // tables, their columns, their keys, and the indexes over them. Anything else
23
- // in the file is reported rather than skipped, because a migration this does
24
- // not understand is a schema the catalog is quietly wrong about.
25
-
26
- type relation struct {
27
- table catalog.Table
28
- indexes []catalog.TableIndex
29
- notNull map[string]bool
30
- constraints map[string]constraintEffect
31
- }
32
-
33
- type constraintEffect struct {
34
- kind nodes.ConstrType
35
- columns []string
36
- }
37
-
38
- type declaredView struct {
39
- view
40
- source string
41
- }
42
-
43
- // ddlState is the schema after every migration applied so far. Keeping it
44
- // across files is the important distinction from the old extractor, which
45
- // emitted every CREATE it saw and could neither attach a later index nor
46
- // account for ALTER, RENAME and DROP.
47
- type ddlState struct {
48
- relations []relation
49
- relationAt map[string]int
50
- views []declaredView
51
- viewAt map[string]int
52
- enums map[string][]string
53
- }
54
-
55
- func newDDLState() *ddlState {
56
- return &ddlState{relationAt: map[string]int{}, viewAt: map[string]int{}, enums: map[string][]string{}}
57
- }
58
-
59
- // readDDL turns one migration into the tables and views it creates, in the
60
- // order it creates them.
61
- func readDDL(sql, source string) ([]relation, []view, []string, error) {
62
- state := newDDLState()
63
- unread, err := state.apply(sql, source)
64
- if err != nil {
65
- return nil, nil, nil, err
66
- }
67
- views := make([]view, 0, len(state.views))
68
- for _, held := range state.views {
69
- views = append(views, held.view)
70
- }
71
- return state.relations, views, unread, nil
72
- }
73
-
74
- func (s *ddlState) apply(sql, source string) ([]string, error) {
75
- tree, err := parser.Parse(sql)
76
- if err != nil {
77
- return nil, fmt.Errorf("%s: %w", source, err)
78
- }
79
-
80
- var unread []string
81
-
82
- for _, item := range tree.Items {
83
- switch stmt := item.(type) {
84
- case *nodes.CreateStmt:
85
- s.addRelation(readRelation(stmt))
86
-
87
- case *nodes.CreateSchemaStmt:
88
- // Schemas namespace the relations and types below; they do not need
89
- // a catalog object of their own.
90
-
91
- case *nodes.CreateEnumStmt:
92
- s.enums[qualifiedName(stringList(stmt.TypeName))] = stringList(stmt.Vals)
93
-
94
- case *nodes.AlterEnumStmt:
95
- if note := s.alterEnum(stmt); note != "" {
96
- unread = append(unread, note)
97
- }
98
-
99
- case *nodes.ViewStmt:
100
- s.addView(declaredView{view: readView(stmt, sql), source: source})
101
-
102
- case *nodes.CreateTableAsStmt:
103
- materialized, ok := readMaterializedView(stmt, sql)
104
- if !ok {
105
- unread = append(unread, fmt.Sprintf("%T is not read", stmt))
106
-
107
- continue
108
- }
109
- s.addView(declaredView{view: materialized, source: source})
110
-
111
- case *nodes.IndexStmt:
112
- name := relationName(stmt.Relation)
113
- at, known := s.relationAt[name]
114
- if !known {
115
- unread = append(unread, fmt.Sprintf("index %q is on unknown table %q", stmt.Idxname, name))
116
-
117
- continue
118
- }
119
- s.relations[at].indexes = append(s.relations[at].indexes, readIndex(stmt))
120
-
121
- case *nodes.AlterTableStmt:
122
- unread = append(unread, s.alter(stmt)...)
123
-
124
- case *nodes.RenameStmt:
125
- if note := s.rename(stmt); note != "" {
126
- unread = append(unread, note)
127
- }
128
-
129
- case *nodes.DropStmt:
130
- if note := s.drop(stmt); note != "" {
131
- unread = append(unread, note)
132
- }
133
-
134
- case *nodes.AlterObjectSchemaStmt:
135
- if note := s.moveSchema(stmt); note != "" {
136
- unread = append(unread, note)
137
- }
138
-
139
- default:
140
- unread = append(unread, fmt.Sprintf("%T is not read", stmt))
141
- }
142
- }
143
-
144
- return unread, nil
145
- }
146
-
147
- func readRelation(stmt *nodes.CreateStmt) relation {
148
- r := relation{table: readTable(stmt), notNull: map[string]bool{}, constraints: map[string]constraintEffect{}}
149
- for _, elt := range items(stmt.TableElts) {
150
- switch def := elt.(type) {
151
- case *nodes.ColumnDef:
152
- r.notNull[def.Colname] = def.IsNotNull
153
- for _, held := range items(def.Constraints) {
154
- constraint, ok := held.(*nodes.Constraint)
155
- if !ok {
156
- continue
157
- }
158
- if constraint.Contype == nodes.CONSTR_NOTNULL || constraint.Contype == nodes.CONSTR_PRIMARY {
159
- r.notNull[def.Colname] = true
160
- }
161
- if constraint.Conname != "" {
162
- r.constraints[constraint.Conname] = constraintEffect{kind: constraint.Contype, columns: []string{def.Colname}}
163
- }
164
- }
165
- case *nodes.Constraint:
166
- r.applyConstraint(def)
167
- }
168
- }
169
- return r
170
- }
171
-
172
- func (s *ddlState) addRelation(r relation) {
173
- name := r.table.Name
174
- if at, exists := s.relationAt[name]; exists {
175
- s.relations[at] = r
176
- return
177
- }
178
- s.relationAt[name] = len(s.relations)
179
- s.relations = append(s.relations, r)
180
- }
181
-
182
- func (s *ddlState) addView(v declaredView) {
183
- if at, exists := s.viewAt[v.name]; exists {
184
- s.views[at] = v
185
- return
186
- }
187
- s.viewAt[v.name] = len(s.views)
188
- s.views = append(s.views, v)
189
- }
190
-
191
- func (s *ddlState) alter(stmt *nodes.AlterTableStmt) []string {
192
- if stmt.Relation == nil {
193
- return []string{"ALTER TABLE without a relation is not read"}
194
- }
195
- name := relationName(stmt.Relation)
196
- at, known := s.relationAt[name]
197
- if !known {
198
- return []string{fmt.Sprintf("ALTER TABLE refers to unknown table %q", name)}
199
- }
200
- r := &s.relations[at]
201
- var unread []string
202
- for _, item := range items(stmt.Cmds) {
203
- cmd, ok := item.(*nodes.AlterTableCmd)
204
- if !ok {
205
- unread = append(unread, fmt.Sprintf("ALTER TABLE subcommand %T is not read", item))
206
- continue
207
- }
208
- switch nodes.AlterTableType(cmd.Subtype) {
209
- case nodes.AT_AddColumn:
210
- def, ok := cmd.Def.(*nodes.ColumnDef)
211
- if !ok {
212
- unread = append(unread, fmt.Sprintf("ADD COLUMN %q has unsupported definition %T", cmd.Name, cmd.Def))
213
- continue
214
- }
215
- r.table.Columns = append(r.table.Columns, readColumn(def, false))
216
- r.notNull[def.Colname] = !r.table.Columns[len(r.table.Columns)-1].Nullable
217
- for _, held := range items(def.Constraints) {
218
- if constraint, ok := held.(*nodes.Constraint); ok && constraint.Conname != "" {
219
- r.constraints[constraint.Conname] = constraintEffect{kind: constraint.Contype, columns: []string{def.Colname}}
220
- }
221
- }
222
- case nodes.AT_DropColumn:
223
- r.dropColumn(cmd.Name)
224
- case nodes.AT_SetNotNull:
225
- if column := relationColumn(r, cmd.Name); column != nil {
226
- column.Nullable = false
227
- r.notNull[cmd.Name] = true
228
- } else {
229
- unread = append(unread, fmt.Sprintf("SET NOT NULL refers to unknown column %q", cmd.Name))
230
- }
231
- case nodes.AT_DropNotNull:
232
- if column := relationColumn(r, cmd.Name); column != nil {
233
- column.Nullable = !column.PK
234
- r.notNull[cmd.Name] = false
235
- } else {
236
- unread = append(unread, fmt.Sprintf("DROP NOT NULL refers to unknown column %q", cmd.Name))
237
- }
238
- case nodes.AT_AlterColumnType:
239
- def, ok := cmd.Def.(*nodes.ColumnDef)
240
- column := relationColumn(r, cmd.Name)
241
- if !ok || column == nil {
242
- unread = append(unread, fmt.Sprintf("ALTER COLUMN %q TYPE has unsupported definition %T", cmd.Name, cmd.Def))
243
- continue
244
- }
245
- column.Type = typeName(def.TypeName)
246
- case nodes.AT_AddConstraint:
247
- constraint, ok := cmd.Def.(*nodes.Constraint)
248
- if !ok || !r.applyConstraint(constraint) {
249
- unread = append(unread, fmt.Sprintf("ADD CONSTRAINT %q is not represented", cmd.Name))
250
- }
251
- case nodes.AT_DropConstraint:
252
- if !r.dropConstraint(cmd.Name) {
253
- unread = append(unread, fmt.Sprintf("DROP CONSTRAINT %q cannot be resolved", cmd.Name))
254
- }
255
- default:
256
- unread = append(unread, fmt.Sprintf("ALTER TABLE %q subcommand %d is not read", name, cmd.Subtype))
257
- }
258
- }
259
- return unread
260
- }
261
-
262
- func (r *relation) applyConstraint(c *nodes.Constraint) bool {
263
- columns := stringList(c.Keys)
264
- if c.Contype == nodes.CONSTR_FOREIGN {
265
- columns = stringList(c.FkAttrs)
266
- }
267
- switch c.Contype {
268
- case nodes.CONSTR_PRIMARY:
269
- for _, name := range columns {
270
- if column := relationColumn(r, name); column != nil {
271
- column.PK = true
272
- column.Nullable = false
273
- }
274
- }
275
- case nodes.CONSTR_FOREIGN:
276
- remote := stringList(c.PkAttrs)
277
- for i, name := range columns {
278
- if column := relationColumn(r, name); column != nil && c.Pktable != nil {
279
- column.FK = &catalog.FK{Table: relationName(c.Pktable), OnDelete: deleteAction(c.FkDelaction)}
280
- if i < len(remote) {
281
- column.FK.Column = remote[i]
282
- }
283
- }
284
- }
285
- case nodes.CONSTR_UNIQUE:
286
- r.indexes = append(r.indexes, catalog.TableIndex{Name: c.Conname, Columns: columns, Unique: true})
287
- default:
288
- return false
289
- }
290
- if c.Conname != "" {
291
- r.constraints[c.Conname] = constraintEffect{kind: c.Contype, columns: columns}
292
- }
293
- return true
294
- }
295
-
296
- func (r *relation) dropConstraint(name string) bool {
297
- effect, known := r.constraints[name]
298
- if !known {
299
- return false
300
- }
301
- for _, held := range effect.columns {
302
- if column := relationColumn(r, held); column != nil {
303
- switch effect.kind {
304
- case nodes.CONSTR_PRIMARY:
305
- column.PK = false
306
- column.Nullable = !r.notNull[held]
307
- case nodes.CONSTR_FOREIGN:
308
- column.FK = nil
309
- }
310
- }
311
- }
312
- if effect.kind == nodes.CONSTR_UNIQUE {
313
- indexes := r.indexes[:0]
314
- for _, index := range r.indexes {
315
- if index.Name != name {
316
- indexes = append(indexes, index)
317
- }
318
- }
319
- r.indexes = indexes
320
- }
321
- delete(r.constraints, name)
322
- return true
323
- }
324
-
325
- func (r *relation) dropColumn(name string) {
326
- for i := range r.table.Columns {
327
- if r.table.Columns[i].Name == name {
328
- r.table.Columns = append(r.table.Columns[:i], r.table.Columns[i+1:]...)
329
- break
330
- }
331
- }
332
- delete(r.notNull, name)
333
- indexes := r.indexes[:0]
334
- for _, index := range r.indexes {
335
- dropped := false
336
- for _, held := range index.Columns {
337
- if held == name {
338
- dropped = true
339
- break
340
- }
341
- }
342
- if !dropped {
343
- indexes = append(indexes, index)
344
- }
345
- }
346
- r.indexes = indexes
347
- }
348
-
349
- func relationColumn(r *relation, name string) *catalog.Column {
350
- for i := range r.table.Columns {
351
- if r.table.Columns[i].Name == name {
352
- return &r.table.Columns[i]
353
- }
354
- }
355
- return nil
356
- }
357
-
358
- func (s *ddlState) rename(stmt *nodes.RenameStmt) string {
359
- if stmt.RenameType == nodes.OBJECT_TYPE {
360
- parts, ok := stmt.Object.(*nodes.List)
361
- if !ok {
362
- return "RENAME TYPE without a qualified name is not read"
363
- }
364
- oldName := qualifiedName(stringList(parts))
365
- values, known := s.enums[oldName]
366
- if !known {
367
- return fmt.Sprintf("RENAME TYPE refers to unknown enum %q", oldName)
368
- }
369
- newName := qualifyLike(oldName, stmt.Newname)
370
- delete(s.enums, oldName)
371
- s.enums[newName] = values
372
- for i := range s.relations {
373
- for j := range s.relations[i].table.Columns {
374
- s.relations[i].table.Columns[j].Type = replaceBaseType(s.relations[i].table.Columns[j].Type, oldName, newName)
375
- }
376
- }
377
- return ""
378
- }
379
- if stmt.Relation == nil {
380
- return fmt.Sprintf("%T without a relation is not read", stmt)
381
- }
382
- oldTable := relationName(stmt.Relation)
383
- at, known := s.relationAt[oldTable]
384
- if !known {
385
- return fmt.Sprintf("RENAME refers to unknown table %q", oldTable)
386
- }
387
- if stmt.RenameType == nodes.OBJECT_COLUMN {
388
- r := &s.relations[at]
389
- column := relationColumn(r, stmt.Subname)
390
- if column == nil {
391
- return fmt.Sprintf("RENAME COLUMN refers to unknown column %q", stmt.Subname)
392
- }
393
- column.Name = stmt.Newname
394
- r.notNull[stmt.Newname] = r.notNull[stmt.Subname]
395
- delete(r.notNull, stmt.Subname)
396
- for i := range r.indexes {
397
- for j, name := range r.indexes[i].Columns {
398
- if name == stmt.Subname {
399
- r.indexes[i].Columns[j] = stmt.Newname
400
- }
401
- }
402
- }
403
- for i := range s.relations {
404
- for j := range s.relations[i].table.Columns {
405
- fk := s.relations[i].table.Columns[j].FK
406
- if fk != nil && fk.Table == oldTable && fk.Column == stmt.Subname {
407
- fk.Column = stmt.Newname
408
- }
409
- }
410
- }
411
- for i := range s.views {
412
- for j := range s.views[i].columns {
413
- for k, from := range s.views[i].columns[j].from {
414
- if from == oldTable+"."+stmt.Subname {
415
- s.views[i].columns[j].from[k] = oldTable + "." + stmt.Newname
416
- }
417
- }
418
- }
419
- }
420
- return ""
421
- }
422
- if stmt.RenameType != nodes.OBJECT_TABLE {
423
- return fmt.Sprintf("RENAME object type %d is not read", stmt.RenameType)
424
- }
425
- newTable := qualifyLike(oldTable, stmt.Newname)
426
- s.relations[at].table.Name = newTable
427
- delete(s.relationAt, oldTable)
428
- s.relationAt[newTable] = at
429
- s.renameRelationReferences(oldTable, newTable)
430
- return ""
431
- }
432
-
433
- func (s *ddlState) drop(stmt *nodes.DropStmt) string {
434
- names := objectNames(stmt.Objects)
435
- switch nodes.ObjectType(stmt.RemoveType) {
436
- case nodes.OBJECT_TABLE:
437
- for _, name := range names {
438
- s.removeRelation(name)
439
- }
440
- case nodes.OBJECT_VIEW, nodes.OBJECT_MATVIEW:
441
- for _, name := range names {
442
- s.removeView(name)
443
- }
444
- case nodes.OBJECT_INDEX:
445
- for _, name := range names {
446
- name = lastNamePart(name)
447
- for i := range s.relations {
448
- indexes := s.relations[i].indexes[:0]
449
- for _, index := range s.relations[i].indexes {
450
- if index.Name != name {
451
- indexes = append(indexes, index)
452
- }
453
- }
454
- s.relations[i].indexes = indexes
455
- }
456
- }
457
- case nodes.OBJECT_TYPE:
458
- for _, name := range names {
459
- delete(s.enums, name)
460
- }
461
- default:
462
- return fmt.Sprintf("DROP object type %d is not read", stmt.RemoveType)
463
- }
464
- return ""
465
- }
466
-
467
- func objectNames(list *nodes.List) []string {
468
- var out []string
469
- for _, item := range items(list) {
470
- parts, ok := item.(*nodes.List)
471
- if !ok {
472
- continue
473
- }
474
- name := qualifiedName(stringList(parts))
475
- if name != "" {
476
- out = append(out, name)
477
- }
478
- }
479
- return out
480
- }
481
-
482
- func (s *ddlState) removeRelation(name string) {
483
- at, known := s.relationAt[name]
484
- if !known {
485
- return
486
- }
487
- s.relations = append(s.relations[:at], s.relations[at+1:]...)
488
- s.reindexRelations()
489
- }
490
-
491
- func (s *ddlState) reindexRelations() {
492
- s.relationAt = map[string]int{}
493
- for i := range s.relations {
494
- s.relationAt[s.relations[i].table.Name] = i
495
- }
496
- }
497
-
498
- func (s *ddlState) removeView(name string) {
499
- at, known := s.viewAt[name]
500
- if !known {
501
- return
502
- }
503
- s.views = append(s.views[:at], s.views[at+1:]...)
504
- s.viewAt = map[string]int{}
505
- for i := range s.views {
506
- s.viewAt[s.views[i].name] = i
507
- }
508
- }
509
-
510
- func readTable(stmt *nodes.CreateStmt) catalog.Table {
511
- table := catalog.Table{Name: relationName(stmt.Relation), Columns: []catalog.Column{}}
512
-
513
- // A table constraint sits in the same list as the columns, so PRIMARY KEY
514
- // (a, b) is collected first and applied to the columns it names.
515
- keys := map[string]bool{}
516
- for _, elt := range items(stmt.TableElts) {
517
- constraint, ok := elt.(*nodes.Constraint)
518
- if !ok || constraint.Contype != nodes.CONSTR_PRIMARY {
519
- continue
520
- }
521
- for _, key := range stringList(constraint.Keys) {
522
- keys[key] = true
523
- }
524
- }
525
-
526
- for _, elt := range items(stmt.TableElts) {
527
- column, ok := elt.(*nodes.ColumnDef)
528
- if !ok {
529
- continue
530
- }
531
- table.Columns = append(table.Columns, readColumn(column, keys[column.Colname]))
532
- }
533
-
534
- return table
535
- }
536
-
537
- func readColumn(def *nodes.ColumnDef, tableKey bool) catalog.Column {
538
- column := catalog.Column{
539
- Name: def.Colname,
540
- Type: typeName(def.TypeName),
541
- PK: tableKey,
542
- }
543
-
544
- notNull := def.IsNotNull
545
- for _, item := range items(def.Constraints) {
546
- constraint, ok := item.(*nodes.Constraint)
547
- if !ok {
548
- continue
549
- }
550
-
551
- switch constraint.Contype {
552
- case nodes.CONSTR_PRIMARY:
553
- column.PK = true
554
- case nodes.CONSTR_NOTNULL:
555
- notNull = true
556
- case nodes.CONSTR_FOREIGN:
557
- column.FK = readForeignKey(constraint)
558
- }
559
- }
560
-
561
- // A primary key is not null whether or not anybody wrote it down.
562
- column.Nullable = !notNull && !column.PK
563
-
564
- return column
565
- }
566
-
567
- func readForeignKey(constraint *nodes.Constraint) *catalog.FK {
568
- if constraint.Pktable == nil {
569
- return nil
570
- }
571
-
572
- fk := &catalog.FK{Table: relationName(constraint.Pktable), OnDelete: deleteAction(constraint.FkDelaction)}
573
- if referenced := stringList(constraint.PkAttrs); len(referenced) > 0 {
574
- fk.Column = referenced[0]
575
- }
576
-
577
- return fk
578
- }
579
-
580
- // deleteAction spells out the single character the grammar keeps. NO ACTION is
581
- // the default and says nothing, so it is left off rather than written down.
582
- func deleteAction(action byte) string {
583
- switch action {
584
- case 'r':
585
- return "restrict"
586
- case 'c':
587
- return "cascade"
588
- case 'n':
589
- return "set null"
590
- case 'd':
591
- return "set default"
592
- default:
593
- return ""
594
- }
595
- }
596
-
597
- func readIndex(stmt *nodes.IndexStmt) catalog.TableIndex {
598
- index := catalog.TableIndex{Name: stmt.Idxname, Unique: stmt.Unique, Columns: []string{}}
599
-
600
- for _, item := range items(stmt.IndexParams) {
601
- elem, ok := item.(*nodes.IndexElem)
602
- if !ok {
603
- continue
604
- }
605
- if elem.Name != "" {
606
- index.Columns = append(index.Columns, elem.Name)
607
-
608
- continue
609
- }
610
- // An index over an expression rather than a column. It has no column
611
- // name to give, and calling it one would be a lie.
612
- index.Columns = append(index.Columns, "(expression)")
613
- }
614
-
615
- return index
616
- }
617
-
618
- // typeName renders the type as the migration wrote it: `text`, `timestamptz`,
619
- // `char(3)`. Never normalised - a reader comparing a column to the file it came
620
- // from wants the string that is in the file.
621
- func typeName(name *nodes.TypeName) string {
622
- if name == nil {
623
- return ""
624
- }
625
-
626
- parts := stringList(name.Names)
627
- // The grammar qualifies the types it knows; the qualification is noise to
628
- // anyone reading a schema.
629
- if len(parts) > 1 && parts[0] == "pg_catalog" {
630
- parts = parts[1:]
631
- }
632
-
633
- rendered := qualifiedName(parts)
634
- if spelled, ok := spelling[rendered]; ok {
635
- rendered = spelled
636
- }
637
-
638
- if mods := typeMods(name.Typmods); mods != "" {
639
- rendered += "(" + mods + ")"
640
- }
641
- for range items(name.ArrayBounds) {
642
- rendered += "[]"
643
- }
644
-
645
- return rendered
646
- }
647
-
648
- func relationName(relation *nodes.RangeVar) string {
649
- if relation == nil {
650
- return ""
651
- }
652
- if relation.Schemaname == "" || relation.Schemaname == "public" {
653
- return relation.Relname
654
- }
655
- return relation.Schemaname + "." + relation.Relname
656
- }
657
-
658
- func qualifiedName(parts []string) string {
659
- if len(parts) > 1 && parts[0] == "public" {
660
- parts = parts[1:]
661
- }
662
- return strings.Join(parts, ".")
663
- }
664
-
665
- func qualifyLike(oldName, newBase string) string {
666
- if at := strings.LastIndex(oldName, "."); at >= 0 {
667
- return oldName[:at+1] + newBase
668
- }
669
- return newBase
670
- }
671
-
672
- func replaceBaseType(held, oldName, newName string) string {
673
- base, arrays := arrayType(held)
674
- if base == oldName {
675
- return newName + arrays
676
- }
677
- return held
678
- }
679
-
680
- func (s *ddlState) renderType(held string) string {
681
- base, arrays := arrayType(held)
682
- values, ok := s.enums[base]
683
- if !ok {
684
- return held
685
- }
686
- return base + " enum(" + strings.Join(values, " | ") + ")" + arrays
687
- }
688
-
689
- func arrayType(held string) (string, string) {
690
- base := held
691
- arrays := ""
692
- for strings.HasSuffix(base, "[]") {
693
- base = strings.TrimSuffix(base, "[]")
694
- arrays += "[]"
695
- }
696
- return base, arrays
697
- }
698
-
699
- func (s *ddlState) alterEnum(stmt *nodes.AlterEnumStmt) string {
700
- name := qualifiedName(stringList(stmt.Typname))
701
- values, known := s.enums[name]
702
- if !known {
703
- return fmt.Sprintf("ALTER TYPE refers to unknown enum %q", name)
704
- }
705
- if stmt.Oldval != "" {
706
- for i, value := range values {
707
- if value == stmt.Oldval {
708
- values[i] = stmt.Newval
709
- s.enums[name] = values
710
- return ""
711
- }
712
- }
713
- return fmt.Sprintf("ALTER TYPE %q refers to unknown enum value %q", name, stmt.Oldval)
714
- }
715
- for _, value := range values {
716
- if value == stmt.Newval {
717
- if stmt.SkipIfNewvalExists {
718
- return ""
719
- }
720
- return fmt.Sprintf("ALTER TYPE %q adds duplicate enum value %q", name, stmt.Newval)
721
- }
722
- }
723
- at := len(values)
724
- if stmt.NewvalNeighbor != "" {
725
- at = -1
726
- for i, value := range values {
727
- if value == stmt.NewvalNeighbor {
728
- at = i
729
- if stmt.NewvalIsAfter {
730
- at++
731
- }
732
- break
733
- }
734
- }
735
- if at < 0 {
736
- return fmt.Sprintf("ALTER TYPE %q names unknown neighbor %q", name, stmt.NewvalNeighbor)
737
- }
738
- }
739
- values = append(values, "")
740
- copy(values[at+1:], values[at:])
741
- values[at] = stmt.Newval
742
- s.enums[name] = values
743
- return ""
744
- }
745
-
746
- func (s *ddlState) moveSchema(stmt *nodes.AlterObjectSchemaStmt) string {
747
- if stmt.ObjectType == nodes.OBJECT_TYPE {
748
- parts, ok := stmt.Object.(*nodes.List)
749
- if !ok {
750
- return "ALTER TYPE SET SCHEMA without a qualified name is not read"
751
- }
752
- oldName := qualifiedName(stringList(parts))
753
- values, known := s.enums[oldName]
754
- if !known {
755
- return fmt.Sprintf("ALTER TYPE SET SCHEMA refers to unknown enum %q", oldName)
756
- }
757
- newName := qualifyInSchema(stmt.Newschema, lastNamePart(oldName))
758
- delete(s.enums, oldName)
759
- s.enums[newName] = values
760
- for i := range s.relations {
761
- for j := range s.relations[i].table.Columns {
762
- s.relations[i].table.Columns[j].Type = replaceBaseType(s.relations[i].table.Columns[j].Type, oldName, newName)
763
- }
764
- }
765
- return ""
766
- }
767
- if stmt.Relation == nil {
768
- return "ALTER ... SET SCHEMA without a relation is not read"
769
- }
770
- oldName := relationName(stmt.Relation)
771
- if stmt.ObjectType != nodes.OBJECT_TABLE && stmt.ObjectType != nodes.OBJECT_VIEW && stmt.ObjectType != nodes.OBJECT_MATVIEW {
772
- return fmt.Sprintf("ALTER object type %d SET SCHEMA is not read", stmt.ObjectType)
773
- }
774
- newName := qualifyInSchema(stmt.Newschema, stmt.Relation.Relname)
775
- if at, known := s.relationAt[oldName]; known {
776
- s.relations[at].table.Name = newName
777
- delete(s.relationAt, oldName)
778
- s.relationAt[newName] = at
779
- s.renameRelationReferences(oldName, newName)
780
- return ""
781
- }
782
- if at, known := s.viewAt[oldName]; known {
783
- s.views[at].name = newName
784
- delete(s.viewAt, oldName)
785
- s.viewAt[newName] = at
786
- return ""
787
- }
788
- return fmt.Sprintf("ALTER SET SCHEMA refers to unknown relation %q", oldName)
789
- }
790
-
791
- func qualifyInSchema(schema, base string) string {
792
- if schema == "" || schema == "public" {
793
- return base
794
- }
795
- return schema + "." + base
796
- }
797
-
798
- func lastNamePart(name string) string {
799
- if at := strings.LastIndex(name, "."); at >= 0 {
800
- return name[at+1:]
801
- }
802
- return name
803
- }
804
-
805
- func (s *ddlState) renameRelationReferences(oldName, newName string) {
806
- for i := range s.relations {
807
- for j := range s.relations[i].table.Columns {
808
- if fk := s.relations[i].table.Columns[j].FK; fk != nil && fk.Table == oldName {
809
- fk.Table = newName
810
- }
811
- }
812
- }
813
- for i := range s.views {
814
- for j, read := range s.views[i].reads {
815
- if read == oldName {
816
- s.views[i].reads[j] = newName
817
- }
818
- }
819
- for j := range s.views[i].columns {
820
- for k, from := range s.views[i].columns[j].from {
821
- if strings.HasPrefix(from, oldName+".") {
822
- s.views[i].columns[j].from[k] = newName + strings.TrimPrefix(from, oldName)
823
- }
824
- }
825
- }
826
- }
827
- }
828
-
829
- // spelling puts back the words a migration was written with.
830
- //
831
- // The grammar normalises the standard type names to PostgreSQL's internal
832
- // ones - `bigint` is parsed as `int8`, `boolean` as `bool` - and the catalog
833
- // asks for the type as declared, because a reader comparing a column to the
834
- // file it came from wants the string that is in the file.
835
- //
836
- // It is a table of the handful the grammar rewrites, not a normaliser of its
837
- // own: a type this does not know passes through exactly as parsed. What it
838
- // cannot do is tell a migration that wrote `int8` from one that wrote
839
- // `bigint`, and it answers `bigint` for both.
840
- var spelling = map[string]string{
841
- "int2": "smallint",
842
- "int4": "integer",
843
- "int8": "bigint",
844
- "float4": "real",
845
- "float8": "double precision",
846
- "bool": "boolean",
847
- "bpchar": "char",
848
- "timestamp": "timestamp",
849
- "timestamptz": "timestamptz",
850
- }
851
-
852
- // typeMods renders the length or precision a type was declared with.
853
- //
854
- // The grammar keeps them two ways - `char(3)` carries a bare integer, while
855
- // `numeric(10,2)` wraps each one in a constant - so both are read. A modifier
856
- // that is neither is left out rather than guessed at.
857
- func typeMods(list *nodes.List) string {
858
- var mods []string
859
-
860
- for _, item := range items(list) {
861
- switch value := item.(type) {
862
- case *nodes.Integer:
863
- mods = append(mods, fmt.Sprint(value.Ival))
864
- case *nodes.A_Const:
865
- if inner, ok := value.Val.(*nodes.Integer); ok {
866
- mods = append(mods, fmt.Sprint(inner.Ival))
867
- }
868
- }
869
- }
870
-
871
- return strings.Join(mods, ",")
872
- }
873
-
874
- func items(list *nodes.List) []nodes.Node {
875
- if list == nil {
876
- return nil
877
- }
878
-
879
- return list.Items
880
- }
881
-
882
- // stringList pulls the string nodes out of a list, which is how the grammar
883
- // keeps qualified names and key lists.
884
- func stringList(list *nodes.List) []string {
885
- var out []string
886
- for _, item := range items(list) {
887
- if value, ok := item.(*nodes.String); ok {
888
- out = append(out, value.Str)
889
- }
890
- }
891
-
892
- return out
893
- }