@shortlink-org/portolan 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +27 -10
- package/scripts/local-api.test.mjs +85 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +24 -3
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +17 -9
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /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
|
-
}
|