@intentius/chant-lexicon-sql 0.124.31 → 0.124.33
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/dist/change-set.js +250 -0
- package/dist/clickhouse/access.js +722 -0
- package/dist/clickhouse/apply/apply.js +243 -0
- package/dist/clickhouse/apply/mutations.js +66 -0
- package/dist/clickhouse/apply/statements.js +545 -0
- package/dist/clickhouse/catalog-types.js +7 -0
- package/dist/clickhouse/container.js +152 -0
- package/dist/clickhouse/engine-syntax.js +159 -0
- package/dist/clickhouse/entities.js +593 -0
- package/dist/clickhouse/expressions.js +107 -0
- package/dist/clickhouse/http.js +55 -0
- package/dist/clickhouse/import/generator.js +192 -0
- package/dist/clickhouse/import/inlined.js +207 -0
- package/dist/clickhouse/import/ir.js +160 -0
- package/dist/clickhouse/import/live-export.js +131 -0
- package/dist/clickhouse/import/parser.js +17 -0
- package/dist/clickhouse/live/bind.js +116 -0
- package/dist/clickhouse/live/catalog.js +223 -0
- package/dist/clickhouse/live/describe-resources.js +91 -0
- package/dist/clickhouse/live/undeclarable.js +59 -0
- package/dist/clickhouse/overlays/codecs.js +57 -0
- package/dist/clickhouse/overlays/engines.js +101 -0
- package/dist/clickhouse/overlays/kinds.js +1 -0
- package/dist/clickhouse/overlays/projections.js +26 -0
- package/dist/clickhouse/overlays/skip-indexes.js +40 -0
- package/dist/clickhouse/overlays/ttl.js +34 -0
- package/dist/clickhouse/overlays/types.js +59 -0
- package/dist/clickhouse/ownership.js +83 -0
- package/dist/clickhouse/parser.js +674 -0
- package/dist/clickhouse/plan/commands.js +159 -0
- package/dist/clickhouse/plan/deep.js +345 -0
- package/dist/clickhouse/plan/diff.js +312 -0
- package/dist/clickhouse/plan/disruption.js +73 -0
- package/dist/clickhouse/plan/normalize.js +545 -0
- package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
- package/dist/clickhouse/plan/report.js +26 -0
- package/dist/clickhouse/plan/rules.js +72 -0
- package/dist/clickhouse/plan/schema.js +67 -0
- package/dist/clickhouse/plan/server-format.js +35 -0
- package/dist/clickhouse/rebuild/backfill.js +265 -0
- package/dist/clickhouse/rebuild/engines.js +146 -0
- package/dist/clickhouse/rebuild/observe.js +260 -0
- package/dist/clickhouse/rebuild/op.js +152 -0
- package/dist/clickhouse/rebuild/partitions.js +65 -0
- package/dist/clickhouse/rebuild/receipts.js +120 -0
- package/dist/clickhouse/rebuild/replicas.js +121 -0
- package/dist/clickhouse/rebuild/shards.js +76 -0
- package/dist/clickhouse/rebuild/steps.js +383 -0
- package/dist/clickhouse/rebuild/verify.js +279 -0
- package/dist/clickhouse/row-types.js +151 -0
- package/dist/clickhouse/testing/fake-server.js +47 -0
- package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
- package/dist/clickhouse/testing/writable-server.js +123 -0
- package/dist/clickhouse/tokens.js +23 -0
- package/dist/clickhouse/topology.js +463 -0
- package/dist/clickhouse/type-syntax.js +73 -0
- package/dist/clickhouse.js +25 -0
- package/dist/codegen/classifier-page.js +93 -0
- package/dist/codegen/clickhouse-module.js +249 -0
- package/dist/codegen/coverage-page.js +70 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +222 -0
- package/dist/codegen/generate-cli.js +13 -0
- package/dist/codegen/generate.js +97 -0
- package/dist/codegen/package.js +28 -0
- package/dist/codegen/postgres-classifier-page.js +95 -0
- package/dist/codegen/postgres-module.js +418 -0
- package/dist/codegen/registry.js +52 -0
- package/dist/composites/audit-log-table.js +35 -0
- package/dist/composites/catalog.js +524 -0
- package/dist/composites/cdc-mirror.js +32 -0
- package/dist/composites/clickhouse.js +6 -0
- package/dist/composites/events-table.js +26 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/join-table.js +25 -0
- package/dist/composites/postgres.js +6 -0
- package/dist/composites/refreshed-view.js +24 -0
- package/dist/composites/replacing-table.js +28 -0
- package/dist/composites/rollup-view.js +29 -0
- package/dist/composites/sharded-table.js +27 -0
- package/dist/composites/soft-delete-table.js +34 -0
- package/dist/composites/tenant-table.js +26 -0
- package/dist/composites/typed.js +8 -0
- package/dist/config.js +206 -0
- package/dist/core/apply.js +93 -0
- package/dist/core/classifier.js +115 -0
- package/dist/core/container.js +69 -0
- package/dist/core/cursor.js +261 -0
- package/dist/core/diff.js +48 -0
- package/dist/core/entity.js +58 -0
- package/dist/core/find-templates.js +118 -0
- package/dist/core/foreign-tables.js +27 -0
- package/dist/core/handoff.js +32 -0
- package/dist/core/index.js +64 -0
- package/dist/core/interpolation.js +191 -0
- package/dist/core/normalize.js +43 -0
- package/dist/core/ownership.js +119 -0
- package/dist/core/receipts.js +22 -0
- package/dist/core/references.js +61 -0
- package/dist/core/statements.js +42 -0
- package/dist/core/template.js +79 -0
- package/dist/core/testing/http-stub.js +21 -0
- package/dist/core/tokens.js +202 -0
- package/dist/core/typed.js +40 -0
- package/dist/core/undeclarable.js +46 -0
- package/dist/coverage.js +161 -0
- package/dist/detect.js +32 -0
- package/dist/dialects.js +18 -0
- package/dist/files/build-root.js +72 -0
- package/dist/files/clickhouse.js +138 -0
- package/dist/files/common.js +58 -0
- package/dist/files/postgres.js +389 -0
- package/dist/generated/clickhouse-types.js +5 -0
- package/dist/generated/clickhouse.js +4361 -0
- package/dist/generated/postgres-types.js +227 -0
- package/dist/generated/postgres.js +3667 -0
- package/dist/import-generator.js +14 -0
- package/dist/import-parser.js +71 -0
- package/dist/index.js +24 -0
- package/dist/init-templates.js +290 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +31 -0
- package/dist/lint/audit-lineage.js +22 -0
- package/dist/lint/post-synth/audit-entries.js +34 -0
- package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
- package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
- package/dist/lint/post-synth/index.js +112 -0
- package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
- package/dist/lint/post-synth/postgres-helpers.js +65 -0
- package/dist/lint/post-synth/postgres-names.js +507 -0
- package/dist/lint/post-synth/sql-helpers.js +23 -0
- package/dist/lint/post-synth/sql101.js +59 -0
- package/dist/lint/post-synth/sql102.js +34 -0
- package/dist/lint/post-synth/sqlch101.js +34 -0
- package/dist/lint/post-synth/sqlch102.js +23 -0
- package/dist/lint/post-synth/sqlch103.js +45 -0
- package/dist/lint/post-synth/sqlch104.js +41 -0
- package/dist/lint/post-synth/sqlch105.js +36 -0
- package/dist/lint/post-synth/sqlch106.js +27 -0
- package/dist/lint/post-synth/sqlch107.js +39 -0
- package/dist/lint/post-synth/sqlch108.js +28 -0
- package/dist/lint/post-synth/sqlch109.js +21 -0
- package/dist/lint/post-synth/sqlch110.js +31 -0
- package/dist/lint/post-synth/sqlch111.js +41 -0
- package/dist/lint/post-synth/sqlch112.js +28 -0
- package/dist/lint/post-synth/sqlch113.js +19 -0
- package/dist/lint/post-synth/sqlch114.js +26 -0
- package/dist/lint/post-synth/sqlch115.js +30 -0
- package/dist/lint/post-synth/sqlch116.js +22 -0
- package/dist/lint/post-synth/sqlch117.js +26 -0
- package/dist/lint/post-synth/sqlch118.js +27 -0
- package/dist/lint/post-synth/sqlch119.js +32 -0
- package/dist/lint/post-synth/sqlch120.js +20 -0
- package/dist/lint/post-synth/sqlch121.js +11 -0
- package/dist/lint/post-synth/sqlch122.js +11 -0
- package/dist/lint/post-synth/sqlch123.js +10 -0
- package/dist/lint/post-synth/sqlch124.js +21 -0
- package/dist/lint/post-synth/sqlch125.js +55 -0
- package/dist/lint/post-synth/sqlch126.js +62 -0
- package/dist/lint/post-synth/sqlch127.js +132 -0
- package/dist/lint/post-synth/sqlpg101.js +25 -0
- package/dist/lint/post-synth/sqlpg102.js +35 -0
- package/dist/lint/post-synth/sqlpg103.js +23 -0
- package/dist/lint/post-synth/sqlpg104.js +23 -0
- package/dist/lint/post-synth/sqlpg105.js +22 -0
- package/dist/lint/post-synth/sqlpg106.js +22 -0
- package/dist/lint/post-synth/sqlpg107.js +22 -0
- package/dist/lint/post-synth/sqlpg108.js +28 -0
- package/dist/lint/post-synth/sqlpg109.js +39 -0
- package/dist/lint/post-synth/sqlpg110.js +38 -0
- package/dist/lint/post-synth/sqlpg111.js +23 -0
- package/dist/lint/post-synth/sqlpg112.js +25 -0
- package/dist/lint/post-synth/sqlpg113.js +29 -0
- package/dist/lint/post-synth/sqlpg114.js +39 -0
- package/dist/lint/post-synth/sqlpg115.js +48 -0
- package/dist/lint/post-synth/sqlpg116.js +26 -0
- package/dist/lint/post-synth/sqlpg117.js +24 -0
- package/dist/lint/post-synth/sqlpg118.js +22 -0
- package/dist/lint/post-synth/sqlpg119.js +41 -0
- package/dist/lint/post-synth/sqlpg120.js +83 -0
- package/dist/lint/post-synth/sqlpg121.js +33 -0
- package/dist/lint/post-synth/sqlpg122.js +27 -0
- package/dist/lint/post-synth/sqlpg123.js +69 -0
- package/dist/lint/post-synth/sqlpg124.js +50 -0
- package/dist/lint/post-synth/sqlpg125.js +41 -0
- package/dist/lint/post-synth/sqlpg126.js +38 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/postgres-templates.js +27 -0
- package/dist/lint/rules/sqlch001.js +79 -0
- package/dist/lint/rules/sqlch002.js +124 -0
- package/dist/lint/rules/sqlch003.js +69 -0
- package/dist/lint/rules/sqlch004.js +83 -0
- package/dist/lint/rules/sqlpg001.js +68 -0
- package/dist/lint/rules/sqlpg002.js +69 -0
- package/dist/lint/rules/sqlpg003.js +58 -0
- package/dist/lint/rules/sqlpg004.js +77 -0
- package/dist/lint/rules/sqlpg005.js +86 -0
- package/dist/lint/rules/templates.js +25 -0
- package/dist/lint/rules/typed-names.js +132 -0
- package/dist/live-dialect.js +32 -0
- package/dist/lsp/catalog.js +44 -0
- package/dist/lsp/completions.js +136 -0
- package/dist/lsp/hover.js +115 -0
- package/dist/lsp/postgres-catalog.js +176 -0
- package/dist/lsp/postgres-template.js +171 -0
- package/dist/lsp/postgres.js +266 -0
- package/dist/lsp/refs.js +60 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +19 -0
- package/dist/lsp/template.js +143 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +293 -0
- package/dist/mcp/postgres.js +117 -0
- package/dist/migration-statements.js +243 -0
- package/dist/op/activities/clickhouse-apply.js +83 -0
- package/dist/op/activities/clickhouse-emulator.js +53 -0
- package/dist/op/activities/clickhouse-rebuild.js +119 -0
- package/dist/op/activities/index.js +26 -0
- package/dist/op/activities/postgres-apply.js +126 -0
- package/dist/op/activities/postgres-emulator.js +70 -0
- package/dist/op/activities/postgres-migration.js +156 -0
- package/dist/op/activities/sql-exec.js +57 -0
- package/dist/op/activity-contracts.js +103 -0
- package/dist/package-cli.js +14 -0
- package/dist/plugin.js +259 -0
- package/dist/postgres/access/acl.js +344 -0
- package/dist/postgres/access/changes.js +30 -0
- package/dist/postgres/access/import.js +61 -0
- package/dist/postgres/access/live.js +96 -0
- package/dist/postgres/apply/apply.js +557 -0
- package/dist/postgres/apply/statements.js +882 -0
- package/dist/postgres/catalog-types.js +18 -0
- package/dist/postgres/container.js +58 -0
- package/dist/postgres/entities.js +1390 -0
- package/dist/postgres/entity-types.js +28 -0
- package/dist/postgres/import/generator.js +283 -0
- package/dist/postgres/import/ir.js +53 -0
- package/dist/postgres/import/live-export.js +83 -0
- package/dist/postgres/keywords.js +32 -0
- package/dist/postgres/live/bind.js +130 -0
- package/dist/postgres/live/catalog-kinds.js +301 -0
- package/dist/postgres/live/catalog.js +644 -0
- package/dist/postgres/live/client.js +151 -0
- package/dist/postgres/live/describe-resources.js +135 -0
- package/dist/postgres/live/undeclarable.js +111 -0
- package/dist/postgres/migrate/backfill.js +170 -0
- package/dist/postgres/migrate/batches.js +122 -0
- package/dist/postgres/migrate/carry.js +518 -0
- package/dist/postgres/migrate/handoff.js +59 -0
- package/dist/postgres/migrate/names.js +61 -0
- package/dist/postgres/migrate/observe.js +360 -0
- package/dist/postgres/migrate/op.js +178 -0
- package/dist/postgres/migrate/receipts.js +117 -0
- package/dist/postgres/migrate/replication.js +106 -0
- package/dist/postgres/migrate/steps.js +514 -0
- package/dist/postgres/overlays/columns.js +31 -0
- package/dist/postgres/overlays/constraints.js +52 -0
- package/dist/postgres/overlays/extension-objects.js +88 -0
- package/dist/postgres/overlays/indexes.js +25 -0
- package/dist/postgres/overlays/kinds.js +2 -0
- package/dist/postgres/overlays/partitioning.js +23 -0
- package/dist/postgres/overlays/relations.js +25 -0
- package/dist/postgres/overlays/storage.js +65 -0
- package/dist/postgres/overlays/type-ddl.js +36 -0
- package/dist/postgres/overlays/types.js +74 -0
- package/dist/postgres/parser.js +1824 -0
- package/dist/postgres/plan/commands.js +137 -0
- package/dist/postgres/plan/deep.js +324 -0
- package/dist/postgres/plan/diff.js +499 -0
- package/dist/postgres/plan/disruption.js +64 -0
- package/dist/postgres/plan/normalize.js +889 -0
- package/dist/postgres/plan/report.js +21 -0
- package/dist/postgres/plan/rules.js +115 -0
- package/dist/postgres/plan/schema.js +105 -0
- package/dist/postgres/plan/server-normalize.js +166 -0
- package/dist/postgres/providers/aurora.js +44 -0
- package/dist/postgres/providers/azure.js +53 -0
- package/dist/postgres/providers/cloud-sql.js +41 -0
- package/dist/postgres/providers/index.js +73 -0
- package/dist/postgres/providers/neon.js +44 -0
- package/dist/postgres/providers/rds.js +47 -0
- package/dist/postgres/providers/supabase.js +45 -0
- package/dist/postgres/providers/types.js +21 -0
- package/dist/postgres/row-types.js +107 -0
- package/dist/postgres/testing/fake-client.js +34 -0
- package/dist/postgres/testing/migration.js +48 -0
- package/dist/postgres/testing/roundtrip-schema.js +64 -0
- package/dist/postgres/testing/server.js +47 -0
- package/dist/postgres/testing/writable-server.js +160 -0
- package/dist/postgres/tokens.js +41 -0
- package/dist/postgres.js +15 -0
- package/dist/read/clickhouse.js +82 -0
- package/dist/read/objects.js +44 -0
- package/dist/read/postgres.js +104 -0
- package/dist/read.js +52 -0
- package/dist/receipts.js +207 -0
- package/dist/serializer.js +172 -0
- package/dist/skill-defs.js +93 -0
- package/dist/spec/catalog.js +191 -0
- package/dist/spec/fetch.d.ts.map +1 -1
- package/dist/spec/fetch.js +90 -0
- package/dist/spec/pin.js +42 -0
- package/dist/spec/postgres-catalog.js +171 -0
- package/dist/spec/postgres-fetch.js +91 -0
- package/dist/spec/postgres-pin.js +82 -0
- package/dist/sql-files.js +206 -0
- package/dist/sql-types-command.js +97 -0
- package/dist/sql-types.js +323 -0
- package/dist/token-source.js +156 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate.js +151 -0
- package/dist/workspace-outputs.js +191 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/lint/rules/typescript-7.test.ts +2 -1
- package/src/lsp/catalog.ts +2 -2
- package/src/lsp/postgres-catalog.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/mcp/index.ts +4 -2
- package/src/spec/fetch.ts +2 -2
- package/src/spec/postgres-fetch.ts +2 -2
|
@@ -0,0 +1,1390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Postgres entities: `schema`, `table`, `index`, `view`, `sequence`,
|
|
3
|
+
* `type` (enum, composite and range), `domain`, `extension`, `func`,
|
|
4
|
+
* `procedure`, `aggregate`, `trigger`, `eventTrigger`, `collation`, `cast`,
|
|
5
|
+
* `policy`, `role` and `grant` tagged templates that parse their DDL at fold
|
|
6
|
+
* time into one Declarable each (chant #3278, #3279, #3680, #3681, #3911).
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { schema, table, index } from "@intentius/chant-lexicon-sql/postgres";
|
|
10
|
+
*
|
|
11
|
+
* export const app = schema`CREATE SCHEMA app`;
|
|
12
|
+
* export const users = table`
|
|
13
|
+
* CREATE TABLE ${app}.users (
|
|
14
|
+
* id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
15
|
+
* email text NOT NULL UNIQUE
|
|
16
|
+
* );
|
|
17
|
+
* COMMENT ON TABLE ${app}.users IS 'One row per account'`;
|
|
18
|
+
* export const usersEmail = index`CREATE INDEX users_email_idx ON ${users} (lower(${users.columns.email}))`;
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The export name is the object's identity in chant; the name in the SQL is
|
|
22
|
+
* its name in the database. An unquoted name folds to lower case, as Postgres
|
|
23
|
+
* folds it, so `CREATE TABLE Users` is `users` and `.columns` is keyed by the
|
|
24
|
+
* folded name.
|
|
25
|
+
*
|
|
26
|
+
* What an interpolation means is decided by its value, as for ClickHouse
|
|
27
|
+
* (#3235):
|
|
28
|
+
*
|
|
29
|
+
* - a Postgres entity is a reference to that object and renders as its
|
|
30
|
+
* schema-qualified name; a schema renders as its name, so `${app}.users`
|
|
31
|
+
* reads `app.users`;
|
|
32
|
+
* - `entity.columns.<name>` is a reference to one column, and renders as the
|
|
33
|
+
* column's name (write `u.${users.columns.id}` in a join);
|
|
34
|
+
* - a sequence inside `nextval(...)`, `currval(...)` or `setval(...)`, and any
|
|
35
|
+
* object followed by `::regclass`, renders as a `regclass` literal:
|
|
36
|
+
* `DEFAULT nextval(${ticketSeq})` reads `nextval('app.ticket_seq'::regclass)`,
|
|
37
|
+
* which is also how the catalog prints it, and the table depends on the
|
|
38
|
+
* sequence;
|
|
39
|
+
* - a string is SQL text, spliced into the statement before it is parsed;
|
|
40
|
+
* - a number or bigint is a numeric literal, a boolean `true`/`false`, `null`
|
|
41
|
+
* is `NULL`;
|
|
42
|
+
* - `literal(value)` is a quoted string literal (`'` doubled; a backslash is an
|
|
43
|
+
* ordinary character, since standard_conforming_strings is on).
|
|
44
|
+
*
|
|
45
|
+
* A function's or procedure's body is a string constant (`AS $$ ... $$`), kept
|
|
46
|
+
* verbatim. An interpolation inside it renders as text (an object as its
|
|
47
|
+
* schema-qualified name, a column reference as the column's name) and is
|
|
48
|
+
* recorded as a reference, so the routine is created after what its body
|
|
49
|
+
* names: `` func`... AS $$ SELECT count(*) FROM ${users} $$` ``.
|
|
50
|
+
*
|
|
51
|
+
* Postgres has no inline comment clause, so a template may follow its CREATE
|
|
52
|
+
* with `COMMENT ON` statements for the same object, its columns and its
|
|
53
|
+
* constraints. They fold into the entity's `comment`, each column's and each
|
|
54
|
+
* constraint's, and stay in the DDL.
|
|
55
|
+
*/
|
|
56
|
+
import { setInterpolationFields } from "@intentius/chant/provenance";
|
|
57
|
+
import { isTrivia, POSTGRES_LEXICAL, SqlSyntaxError, untokenize } from "./tokens.js";
|
|
58
|
+
import { identValue, parseStatements } from "./parser.js";
|
|
59
|
+
import { keywordCategory, quoteIdent } from "./keywords.js";
|
|
60
|
+
import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, unparsedClauses } from "../core/interpolation.js";
|
|
61
|
+
import { SqlObject, isColumnRefOf, isSqlObjectOf, makeSqlEntity } from "../core/entity.js";
|
|
62
|
+
import { SqlLiteral, SqlTemplateError, describeValue as describe, stripUnset, templateParts } from "../core/template.js";
|
|
63
|
+
import { setTypedSlots } from "../core/typed.js";
|
|
64
|
+
export { SQL_LEXICON } from "../core/entity.js";
|
|
65
|
+
export { SqlLiteral, SqlTemplateError } from "../core/template.js";
|
|
66
|
+
export { quoteIdent } from "./keywords.js";
|
|
67
|
+
export { POSTGRES_ENTITY_TYPES } from "./entity-types.js";
|
|
68
|
+
import { POSTGRES_ENTITY_TYPES } from "./entity-types.js";
|
|
69
|
+
/**
|
|
70
|
+
* A string as a quoted Postgres string literal: `'` doubled, a backslash left
|
|
71
|
+
* as written (standard_conforming_strings is on). An interpolated plain string
|
|
72
|
+
* is SQL text; wrap it in `literal()` when it is a value.
|
|
73
|
+
*/
|
|
74
|
+
export function literal(value) {
|
|
75
|
+
if (value === null)
|
|
76
|
+
return new SqlLiteral("NULL");
|
|
77
|
+
if (typeof value !== "string")
|
|
78
|
+
return new SqlLiteral(String(value));
|
|
79
|
+
return new SqlLiteral(`'${value.replace(/'/g, "''")}'`);
|
|
80
|
+
}
|
|
81
|
+
// ── Entities ───────────────────────────────────────────────────────────
|
|
82
|
+
/** The members a Postgres entity has besides its props (the shared core's {@link SqlObject}). */
|
|
83
|
+
export class PostgresObject extends SqlObject {
|
|
84
|
+
}
|
|
85
|
+
export function isPostgresObject(value) {
|
|
86
|
+
return isSqlObjectOf(value, "Postgres::");
|
|
87
|
+
}
|
|
88
|
+
/** A column reference: an AttrRef whose parent is a Postgres table or view. */
|
|
89
|
+
export function isColumnRef(value) {
|
|
90
|
+
return isColumnRefOf(value, isPostgresObject);
|
|
91
|
+
}
|
|
92
|
+
// ── Rendering interpolations ───────────────────────────────────────────
|
|
93
|
+
function renderReference(value) {
|
|
94
|
+
if (isPostgresObject(value))
|
|
95
|
+
return value.sqlName;
|
|
96
|
+
if (isColumnRef(value))
|
|
97
|
+
return quoteIdent(value.attribute);
|
|
98
|
+
throw new TypeError(`not a reference: ${describe(value)}`);
|
|
99
|
+
}
|
|
100
|
+
/** A name as a `regclass` literal's text: `'app.ticket_seq'`. */
|
|
101
|
+
const regclassText = (sqlName) => `'${sqlName.replace(/'/g, "''")}'`;
|
|
102
|
+
/** Functions whose first argument is a `regclass`, written as a string (`nextval('s')`). */
|
|
103
|
+
const REGCLASS_FUNCTIONS = ["NEXTVAL", "CURRVAL", "SETVAL"];
|
|
104
|
+
/**
|
|
105
|
+
* Interpolations that stand where Postgres wants a `regclass` literal: the
|
|
106
|
+
* first argument of `nextval`, `currval` or `setval`, rendered
|
|
107
|
+
* `'name'::regclass`; and an object followed by `::regclass`, rendered
|
|
108
|
+
* `'name'`. By value index.
|
|
109
|
+
*/
|
|
110
|
+
function regclassPositions(tokens, values) {
|
|
111
|
+
const out = new Map();
|
|
112
|
+
const sig = tokens.filter((t) => !isTrivia(t));
|
|
113
|
+
sig.forEach((t, k) => {
|
|
114
|
+
if (t.kind !== "ref" || !isPostgresObject(values[t.part]))
|
|
115
|
+
return;
|
|
116
|
+
const prev = sig[k - 1];
|
|
117
|
+
const fn = sig[k - 2];
|
|
118
|
+
const next = sig[k + 1];
|
|
119
|
+
if (prev?.kind === "punct" && prev.text === "(" &&
|
|
120
|
+
fn?.kind === "ident" && REGCLASS_FUNCTIONS.includes(fn.text.toUpperCase()) &&
|
|
121
|
+
next?.kind === "punct" && (next.text === ")" || next.text === ",")) {
|
|
122
|
+
out.set(t.part, "call");
|
|
123
|
+
}
|
|
124
|
+
else if (next?.kind === "op" && next.text === "::" && sig[k + 2]?.kind === "ident" && sig[k + 2].text.toLowerCase() === "regclass") {
|
|
125
|
+
out.set(t.part, "cast");
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The output name Postgres gives a select-list item with no alias
|
|
132
|
+
* (`FigureColname` in the parser): a column's name, a function's name, a
|
|
133
|
+
* cast's operand's name, `case` for a CASE, else `?column?`.
|
|
134
|
+
*/
|
|
135
|
+
function outputOf(items, valueOf) {
|
|
136
|
+
const sig = items.filter((t) => !isTrivia(t));
|
|
137
|
+
if (sig.length === 0)
|
|
138
|
+
return undefined;
|
|
139
|
+
// `x::type` names its output after `x`.
|
|
140
|
+
const cast = sig.findIndex((t) => t.kind === "op" && t.text === "::");
|
|
141
|
+
const head = cast > 0 ? sig.slice(0, cast) : sig;
|
|
142
|
+
const name = (t) => {
|
|
143
|
+
if (t.kind === "ident" || t.kind === "qident")
|
|
144
|
+
return identValue(t);
|
|
145
|
+
const v = valueOf(t);
|
|
146
|
+
return isColumnRef(v) ? v.attribute : undefined;
|
|
147
|
+
};
|
|
148
|
+
const first = head[0];
|
|
149
|
+
const last = head[head.length - 1];
|
|
150
|
+
// A column, bare or qualified: `x`, `t.x`, `${t.columns.x}`, `t.${t.columns.x}`.
|
|
151
|
+
if (head.length === 1 || (head.length === 3 && head[1].kind === "punct" && head[1].text === ".")) {
|
|
152
|
+
const n = name(last);
|
|
153
|
+
if (n !== undefined)
|
|
154
|
+
return n;
|
|
155
|
+
}
|
|
156
|
+
if (first.kind === "ident" && first.text.toUpperCase() === "CASE")
|
|
157
|
+
return "case";
|
|
158
|
+
if (first.kind === "ident" && head[1]?.kind === "punct" && head[1].text === "(")
|
|
159
|
+
return identValue(first);
|
|
160
|
+
return "?column?";
|
|
161
|
+
}
|
|
162
|
+
/** Postgres as the shared template machinery reads it (`../core/interpolation.ts`). */
|
|
163
|
+
export const POSTGRES_TEMPLATES = {
|
|
164
|
+
lexical: POSTGRES_LEXICAL,
|
|
165
|
+
isObject: isPostgresObject,
|
|
166
|
+
isColumnRef,
|
|
167
|
+
renderReference,
|
|
168
|
+
identValue: (t) => identValue(t),
|
|
169
|
+
unnamedOutput: () => "?column?",
|
|
170
|
+
lineage: {
|
|
171
|
+
aliasWithoutAs: true,
|
|
172
|
+
keyword: (t) => {
|
|
173
|
+
const k = keywordCategory(t.text);
|
|
174
|
+
return k === undefined ? undefined : k === "R" ? "reserved" : "keyword";
|
|
175
|
+
},
|
|
176
|
+
distinctOn: true,
|
|
177
|
+
qualifiedRefs: true,
|
|
178
|
+
outputOf,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
/** The statements each tag holds. */
|
|
182
|
+
export const STATEMENTS_OF = {
|
|
183
|
+
schema: ["schema"],
|
|
184
|
+
table: ["table"],
|
|
185
|
+
index: ["index"],
|
|
186
|
+
view: ["view"],
|
|
187
|
+
sequence: ["sequence"],
|
|
188
|
+
type: ["enum", "composite", "range"],
|
|
189
|
+
domain: ["domain"],
|
|
190
|
+
extension: ["extension"],
|
|
191
|
+
func: ["function"],
|
|
192
|
+
procedure: ["procedure"],
|
|
193
|
+
aggregate: ["aggregate"],
|
|
194
|
+
trigger: ["trigger"],
|
|
195
|
+
eventTrigger: ["eventTrigger"],
|
|
196
|
+
collation: ["collation"],
|
|
197
|
+
cast: ["cast"],
|
|
198
|
+
policy: ["policy"],
|
|
199
|
+
role: ["role"],
|
|
200
|
+
grant: ["grant"],
|
|
201
|
+
};
|
|
202
|
+
const TAG_OF = {
|
|
203
|
+
schema: "schema",
|
|
204
|
+
table: "table",
|
|
205
|
+
index: "index",
|
|
206
|
+
view: "view",
|
|
207
|
+
sequence: "sequence",
|
|
208
|
+
enum: "type",
|
|
209
|
+
composite: "type",
|
|
210
|
+
range: "type",
|
|
211
|
+
domain: "domain",
|
|
212
|
+
extension: "extension",
|
|
213
|
+
function: "func",
|
|
214
|
+
procedure: "procedure",
|
|
215
|
+
aggregate: "aggregate",
|
|
216
|
+
trigger: "trigger",
|
|
217
|
+
eventTrigger: "eventTrigger",
|
|
218
|
+
collation: "collation",
|
|
219
|
+
cast: "cast",
|
|
220
|
+
policy: "policy",
|
|
221
|
+
role: "role",
|
|
222
|
+
grant: "grant",
|
|
223
|
+
rowSecurity: undefined,
|
|
224
|
+
comment: undefined,
|
|
225
|
+
};
|
|
226
|
+
/** What each statement kind is called in a message. */
|
|
227
|
+
export const STATEMENT_NAMES = {
|
|
228
|
+
schema: "CREATE SCHEMA",
|
|
229
|
+
table: "CREATE TABLE",
|
|
230
|
+
index: "CREATE INDEX",
|
|
231
|
+
view: "CREATE VIEW",
|
|
232
|
+
sequence: "CREATE SEQUENCE",
|
|
233
|
+
enum: "CREATE TYPE",
|
|
234
|
+
composite: "CREATE TYPE",
|
|
235
|
+
range: "CREATE TYPE",
|
|
236
|
+
domain: "CREATE DOMAIN",
|
|
237
|
+
extension: "CREATE EXTENSION",
|
|
238
|
+
function: "CREATE FUNCTION",
|
|
239
|
+
procedure: "CREATE PROCEDURE",
|
|
240
|
+
aggregate: "CREATE AGGREGATE",
|
|
241
|
+
trigger: "CREATE TRIGGER",
|
|
242
|
+
eventTrigger: "CREATE EVENT TRIGGER",
|
|
243
|
+
collation: "CREATE COLLATION",
|
|
244
|
+
cast: "CREATE CAST",
|
|
245
|
+
policy: "CREATE POLICY",
|
|
246
|
+
role: "CREATE ROLE",
|
|
247
|
+
grant: "GRANT",
|
|
248
|
+
rowSecurity: "ALTER TABLE",
|
|
249
|
+
comment: "COMMENT ON",
|
|
250
|
+
};
|
|
251
|
+
/** Props without their unset fields and empty optional lists, so props hold only what the DDL says. */
|
|
252
|
+
const strip = (o, keep = []) => Object.fromEntries(Object.entries(stripUnset(o)).filter(([k, v]) => keep.includes(k) || !(Array.isArray(v) && v.length === 0)));
|
|
253
|
+
const req = (s) => s ?? "";
|
|
254
|
+
/** A string constant's value: `'...'`, `E'...'`, `$tag$...$tag$`. */
|
|
255
|
+
export function stringValue(s) {
|
|
256
|
+
if (s === undefined)
|
|
257
|
+
return undefined;
|
|
258
|
+
let m = /^'(.*)'$/s.exec(s);
|
|
259
|
+
if (m)
|
|
260
|
+
return m[1].replace(/''/g, "'");
|
|
261
|
+
m = /^[Ee]'(.*)'$/s.exec(s);
|
|
262
|
+
if (m)
|
|
263
|
+
return m[1].replace(/''/g, "'").replace(/\\(.)/gs, (_, c) => ({ n: "\n", t: "\t", r: "\r", b: "\b", f: "\f" })[c] ?? c);
|
|
264
|
+
m = /^\$([A-Za-z_\u0080-][A-Za-z0-9_\u0080-]*)?\$(.*)\$\1\$$/s.exec(s);
|
|
265
|
+
if (m)
|
|
266
|
+
return m[2];
|
|
267
|
+
return s;
|
|
268
|
+
}
|
|
269
|
+
/** A definition list's values, by option name: each value as written, interpolations rendered; an option with no value is `true`. */
|
|
270
|
+
function definitionValues(ctx, elems, path) {
|
|
271
|
+
const out = {};
|
|
272
|
+
for (const e of elems)
|
|
273
|
+
out[e.name] = e.value ? req(text(ctx, e.value, path)) : "true";
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
/** A qualified name's schema and name. An interpolated schema names the schema; an interpolated object gives both. */
|
|
277
|
+
function qualified(ctx, n) {
|
|
278
|
+
const pieces = [];
|
|
279
|
+
const splices = [];
|
|
280
|
+
for (const t of ctx.tokens.slice(n.span.from, n.span.to)) {
|
|
281
|
+
if (isTrivia(t) || (t.kind === "punct" && t.text === "."))
|
|
282
|
+
continue;
|
|
283
|
+
if (t.kind === "ref") {
|
|
284
|
+
const v = ctx.values[t.part];
|
|
285
|
+
if (isPostgresObject(v)) {
|
|
286
|
+
const p = v.props;
|
|
287
|
+
if (v.entityType !== POSTGRES_ENTITY_TYPES.schema && v.entityType !== POSTGRES_ENTITY_TYPES.extension && p.schema) {
|
|
288
|
+
pieces.push(p.schema);
|
|
289
|
+
splices.push(undefined);
|
|
290
|
+
}
|
|
291
|
+
pieces.push(p.name);
|
|
292
|
+
splices.push(undefined);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
pieces.push(renderReference(v));
|
|
296
|
+
splices.push(undefined);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
pieces.push(identValue(t));
|
|
301
|
+
splices.push(t.splice);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const last = pieces.length - 1;
|
|
305
|
+
splices.forEach((splice, i) => {
|
|
306
|
+
if (splice !== undefined)
|
|
307
|
+
ctx.fed[splice].add(i === last ? "name" : "schema");
|
|
308
|
+
});
|
|
309
|
+
return pieces.length >= 2 ? { schema: pieces[pieces.length - 2], name: pieces[pieces.length - 1] } : { name: pieces[0] ?? "" };
|
|
310
|
+
}
|
|
311
|
+
const sqlNameOf = (q) => (q.schema ? `${quoteIdent(q.schema)}.${quoteIdent(q.name)}` : quoteIdent(q.name));
|
|
312
|
+
/** The object a name refers to: the entity when the whole name is one interpolation, else its SQL name. */
|
|
313
|
+
function target(ctx, n) {
|
|
314
|
+
const sig = ctx.tokens.slice(n.span.from, n.span.to).filter((t) => !isTrivia(t));
|
|
315
|
+
if (sig.length === 1 && sig[0].kind === "ref") {
|
|
316
|
+
const v = ctx.values[sig[0].part];
|
|
317
|
+
if (isPostgresObject(v))
|
|
318
|
+
return v;
|
|
319
|
+
}
|
|
320
|
+
return sqlNameOf(qualified(ctx, n));
|
|
321
|
+
}
|
|
322
|
+
/** The SQL name of what a name refers to. */
|
|
323
|
+
const targetName = (ctx, n) => {
|
|
324
|
+
const t = target(ctx, n);
|
|
325
|
+
return typeof t === "string" ? t : t.sqlName;
|
|
326
|
+
};
|
|
327
|
+
/** The name of a column in a key or reference list; an interpolated column reference gives its attribute. */
|
|
328
|
+
function columnName(ctx, c) {
|
|
329
|
+
if (c.name)
|
|
330
|
+
return c.name;
|
|
331
|
+
const t = ctx.tokens[c.span.from];
|
|
332
|
+
const v = t?.kind === "ref" ? ctx.values[t.part] : undefined;
|
|
333
|
+
return isColumnRef(v) ? v.attribute : req(text(ctx, c.span));
|
|
334
|
+
}
|
|
335
|
+
function columnDef(ctx, c) {
|
|
336
|
+
for (const span of [c.nameSpan, c.type, c.default, c.collate, c.compression, c.storage, c.generated?.expr, c.generated?.options])
|
|
337
|
+
feed(ctx, span, "columns");
|
|
338
|
+
const def = { name: columnName(ctx, { name: c.name, span: c.nameSpan }) };
|
|
339
|
+
const set = (k, v) => {
|
|
340
|
+
if (v !== undefined)
|
|
341
|
+
def[k] = v;
|
|
342
|
+
};
|
|
343
|
+
set("type", text(ctx, c.type));
|
|
344
|
+
set("notNull", c.notNull);
|
|
345
|
+
set("notNullName", c.notNullName);
|
|
346
|
+
set("default", text(ctx, c.default));
|
|
347
|
+
if (c.generated) {
|
|
348
|
+
def.generated = stripUnset({
|
|
349
|
+
kind: c.generated.kind,
|
|
350
|
+
expr: text(ctx, c.generated.expr),
|
|
351
|
+
always: c.generated.always,
|
|
352
|
+
options: text(ctx, c.generated.options),
|
|
353
|
+
});
|
|
354
|
+
// `always` false is meaningful for an identity column (BY DEFAULT).
|
|
355
|
+
def.generated.always = c.generated.always;
|
|
356
|
+
}
|
|
357
|
+
set("collate", text(ctx, c.collate));
|
|
358
|
+
set("compression", text(ctx, c.compression));
|
|
359
|
+
set("storage", text(ctx, c.storage));
|
|
360
|
+
return def;
|
|
361
|
+
}
|
|
362
|
+
function deferral(c) {
|
|
363
|
+
return stripUnset({ deferrable: c.deferrable, initiallyDeferred: c.initiallyDeferred });
|
|
364
|
+
}
|
|
365
|
+
function keyDef(ctx, c, columns, path) {
|
|
366
|
+
return strip({
|
|
367
|
+
name: c.name,
|
|
368
|
+
columns,
|
|
369
|
+
nullsNotDistinct: c.nullsNotDistinct,
|
|
370
|
+
include: text(ctx, c.include, path),
|
|
371
|
+
with: text(ctx, c.with, path),
|
|
372
|
+
...deferral(c),
|
|
373
|
+
}, ["columns"]);
|
|
374
|
+
}
|
|
375
|
+
function checkDef(ctx, c) {
|
|
376
|
+
return stripUnset({
|
|
377
|
+
name: c.name,
|
|
378
|
+
expr: req(text(ctx, c.expr, "checks")),
|
|
379
|
+
noInherit: c.noInherit,
|
|
380
|
+
notValid: c.notValid,
|
|
381
|
+
notEnforced: c.notEnforced,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function foreignKeyDef(ctx, c, columns) {
|
|
385
|
+
const r = c.references;
|
|
386
|
+
feed(ctx, r.table.span, "foreignKeys");
|
|
387
|
+
for (const x of r.columns)
|
|
388
|
+
feed(ctx, x.span, "foreignKeys");
|
|
389
|
+
return strip({
|
|
390
|
+
name: c.name,
|
|
391
|
+
columns,
|
|
392
|
+
references: target(ctx, r.table),
|
|
393
|
+
refTable: targetName(ctx, r.table),
|
|
394
|
+
refColumns: r.columns.map((x) => columnName(ctx, x)),
|
|
395
|
+
match: r.match,
|
|
396
|
+
onDelete: text(ctx, r.onDelete),
|
|
397
|
+
onUpdate: text(ctx, r.onUpdate),
|
|
398
|
+
notValid: c.notValid,
|
|
399
|
+
notEnforced: c.notEnforced,
|
|
400
|
+
...deferral(c),
|
|
401
|
+
}, ["columns", "refColumns"]);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* The `${}` in a foreign key's referenced column list that each name one
|
|
405
|
+
* column, paired with the referencing column at the same position (#3751).
|
|
406
|
+
* `chant sql types` types each as a column comparing like that one.
|
|
407
|
+
*/
|
|
408
|
+
function referenceSlots(ctx, c, columns) {
|
|
409
|
+
const out = [];
|
|
410
|
+
c.references.columns.forEach((x, n) => {
|
|
411
|
+
const column = columns[n];
|
|
412
|
+
if (x.name || column === undefined)
|
|
413
|
+
return;
|
|
414
|
+
const sig = ctx.tokens.slice(x.span.from, x.span.to).filter((t) => !isTrivia(t));
|
|
415
|
+
const t = sig[0];
|
|
416
|
+
if (sig.length === 1 && t?.kind === "ref" && isColumnRef(ctx.values[t.part]))
|
|
417
|
+
out.push({ index: t.part, kind: "references", column });
|
|
418
|
+
});
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
function exclusionDef(ctx, c) {
|
|
422
|
+
return stripUnset({
|
|
423
|
+
name: c.name,
|
|
424
|
+
using: c.using,
|
|
425
|
+
elements: req(text(ctx, c.expr, "exclusions")),
|
|
426
|
+
where: text(ctx, c.where, "exclusions"),
|
|
427
|
+
include: text(ctx, c.include, "exclusions"),
|
|
428
|
+
with: text(ctx, c.with, "exclusions"),
|
|
429
|
+
...deferral(c),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Fold the COMMENT ON statements after the CREATE into the object they name:
|
|
434
|
+
* the object itself, one of its columns, or one of its named constraints.
|
|
435
|
+
* Anything else is refused, since a template declares one object.
|
|
436
|
+
*/
|
|
437
|
+
function applyComments(tag, ctx, comments, self, objectTypes, parts = {}) {
|
|
438
|
+
let comment;
|
|
439
|
+
const isSelf = (q) => q.name === self.name && (q.schema === undefined || self.schema === undefined || q.schema === self.schema);
|
|
440
|
+
const refuse = (c, why) => {
|
|
441
|
+
throw new SqlTemplateError(tag, `COMMENT ON ${c.objectType} ${c.target.pieces.filter(Boolean).join(".") || "${...}"} ${why}`, 0, 0);
|
|
442
|
+
};
|
|
443
|
+
for (const c of comments) {
|
|
444
|
+
const value = c.text ? stringValue(text(ctx, c.text, "comment")) : undefined;
|
|
445
|
+
if (c.objectType === "COLUMN") {
|
|
446
|
+
// `table.column` or `schema.table.column`: the last piece is the column, the rest its relation.
|
|
447
|
+
const all = ctx.tokens.slice(c.target.span.from, c.target.span.to).filter((t) => !isTrivia(t) && !(t.kind === "punct" && t.text === "."));
|
|
448
|
+
const last = all[all.length - 1];
|
|
449
|
+
const col = last === undefined ? "" : last.kind === "ref" ? columnName(ctx, { name: "", span: { from: c.target.span.to - 1, to: c.target.span.to, refs: [] } }) : identValue(last);
|
|
450
|
+
if (all.length < 2)
|
|
451
|
+
refuse(c, "needs the relation: COMMENT ON COLUMN table.column");
|
|
452
|
+
const owner = qualified(ctx, { pieces: c.target.pieces.slice(0, -1), span: { from: c.target.span.from, to: lastDot(ctx, c.target.span), refs: c.target.span.refs } });
|
|
453
|
+
if (!isSelf(owner))
|
|
454
|
+
refuse(c, `is not a column of ${sqlNameOf(self)}`);
|
|
455
|
+
if (parts.columnComments) {
|
|
456
|
+
if (value !== undefined)
|
|
457
|
+
parts.columnComments[col] = value;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const column = parts.columns?.get(col);
|
|
461
|
+
if (!column)
|
|
462
|
+
refuse(c, `names no column of ${sqlNameOf(self)}`);
|
|
463
|
+
if (value !== undefined)
|
|
464
|
+
column.comment = value;
|
|
465
|
+
else
|
|
466
|
+
delete column.comment;
|
|
467
|
+
}
|
|
468
|
+
else if (c.objectType === "CONSTRAINT") {
|
|
469
|
+
if (!c.on || !isSelf(qualified(ctx, c.on)))
|
|
470
|
+
refuse(c, `is not on ${sqlNameOf(self)}`);
|
|
471
|
+
const name = qualified(ctx, c.target).name;
|
|
472
|
+
const constraint = parts.constraints?.get(name);
|
|
473
|
+
if (!constraint)
|
|
474
|
+
refuse(c, `names no constraint of ${sqlNameOf(self)}; a constraint commented on needs CONSTRAINT ${name} in the definition`);
|
|
475
|
+
if (value !== undefined)
|
|
476
|
+
constraint.comment = value;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
if (!objectTypes.includes(c.objectType))
|
|
480
|
+
refuse(c, `does not comment on the ${tag} this template declares`);
|
|
481
|
+
if (c.objectType === "CAST") {
|
|
482
|
+
// `COMMENT ON CAST (source AS target)`: the two types, as the CREATE CAST writes them.
|
|
483
|
+
const loose = (x) => x.toLowerCase().replace(/\s+/g, " ").replace(/\s*([(),.])\s*/g, "$1");
|
|
484
|
+
const named = `(${req(text(ctx, c.cast.source))} AS ${req(text(ctx, c.cast.target))})`;
|
|
485
|
+
if (loose(named) !== loose(self.name))
|
|
486
|
+
refuse(c, `names another cast than ${self.name}`);
|
|
487
|
+
}
|
|
488
|
+
else if (c.objectType === "TRIGGER" || c.objectType === "POLICY") {
|
|
489
|
+
// `COMMENT ON TRIGGER name ON table`: the name is the trigger's, the table its own.
|
|
490
|
+
if (qualified(ctx, c.target).name !== self.name || !c.on || !isSelf({ ...qualified(ctx, c.on), name: self.name }))
|
|
491
|
+
refuse(c, `names another ${c.objectType.toLowerCase()} than ${self.name}`);
|
|
492
|
+
}
|
|
493
|
+
else if (!isSelf(qualified(ctx, c.target)))
|
|
494
|
+
refuse(c, `names another object than ${sqlNameOf(self)}`);
|
|
495
|
+
comment = value;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return comment;
|
|
499
|
+
}
|
|
500
|
+
/** The token index of the last `.` in a span, so `a.b.c` without its last piece is `a.b`. */
|
|
501
|
+
function lastDot(ctx, span) {
|
|
502
|
+
for (let i = span.to - 1; i >= span.from; i--) {
|
|
503
|
+
const t = ctx.tokens[i];
|
|
504
|
+
if (t.kind === "punct" && t.text === ".")
|
|
505
|
+
return i;
|
|
506
|
+
}
|
|
507
|
+
return span.from;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The first statement after the CREATE that its template may not hold: a
|
|
511
|
+
* template is one object, then COMMENT ON statements for it; a table's may
|
|
512
|
+
* also turn row-level security on or off (`ALTER TABLE ... ENABLE ROW LEVEL
|
|
513
|
+
* SECURITY`), as `pg_dump` prints it. A grant is one statement.
|
|
514
|
+
*/
|
|
515
|
+
export function followOnStray(tag, rest) {
|
|
516
|
+
if (tag === "grant")
|
|
517
|
+
return rest[0];
|
|
518
|
+
return rest.find((n) => n.statement !== "comment" && !(tag === "table" && n.statement === "rowSecurity"));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* The interpolations that sit inside a string constant (a routine's
|
|
522
|
+
* `AS $$ ... $$` body), rendered into the text around them: a string
|
|
523
|
+
* constant is one token, so the template is joined there before it is
|
|
524
|
+
* tokenized. Returns the joined parts and values, the original index of each
|
|
525
|
+
* value kept, and the objects the inlined interpolations name.
|
|
526
|
+
*/
|
|
527
|
+
function inlineQuoted(parts, values) {
|
|
528
|
+
let state = { kind: "none" };
|
|
529
|
+
const insideAt = [];
|
|
530
|
+
parts.forEach((src, part) => {
|
|
531
|
+
for (let i = 0; i < src.length; i++) {
|
|
532
|
+
const c = src[i];
|
|
533
|
+
const rest = src.slice(i);
|
|
534
|
+
switch (state.kind) {
|
|
535
|
+
case "none": {
|
|
536
|
+
if (c === "'")
|
|
537
|
+
state = { kind: "single", escapes: /[Ee]$/.test(src.slice(0, i)) && !/[A-Za-z0-9_$][Ee]$/.test(src.slice(0, i)) };
|
|
538
|
+
else if (c === '"')
|
|
539
|
+
state = { kind: "dquote" };
|
|
540
|
+
else if (rest.startsWith("--"))
|
|
541
|
+
state = { kind: "line" };
|
|
542
|
+
else if (rest.startsWith("/*")) {
|
|
543
|
+
state = { kind: "block", depth: 1 };
|
|
544
|
+
i++;
|
|
545
|
+
}
|
|
546
|
+
else if (c === "$" && !/[A-Za-z0-9_$]$/.test(src.slice(0, i))) {
|
|
547
|
+
const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(rest);
|
|
548
|
+
if (m) {
|
|
549
|
+
state = { kind: "dollar", tag: m[0] };
|
|
550
|
+
i += m[0].length - 1;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case "single":
|
|
556
|
+
if (state.escapes && c === "\\")
|
|
557
|
+
i++;
|
|
558
|
+
else if (c === "'") {
|
|
559
|
+
if (src[i + 1] === "'")
|
|
560
|
+
i++;
|
|
561
|
+
else
|
|
562
|
+
state = { kind: "none" };
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
case "dquote":
|
|
566
|
+
if (c === '"') {
|
|
567
|
+
if (src[i + 1] === '"')
|
|
568
|
+
i++;
|
|
569
|
+
else
|
|
570
|
+
state = { kind: "none" };
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
case "dollar":
|
|
574
|
+
if (rest.startsWith(state.tag)) {
|
|
575
|
+
i += state.tag.length - 1;
|
|
576
|
+
state = { kind: "none" };
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
case "line":
|
|
580
|
+
if (c === "\n")
|
|
581
|
+
state = { kind: "none" };
|
|
582
|
+
break;
|
|
583
|
+
case "block":
|
|
584
|
+
if (rest.startsWith("/*")) {
|
|
585
|
+
state.depth++;
|
|
586
|
+
i++;
|
|
587
|
+
}
|
|
588
|
+
else if (rest.startsWith("*/")) {
|
|
589
|
+
i++;
|
|
590
|
+
state = state.depth === 1 ? { kind: "none" } : { kind: "block", depth: state.depth - 1 };
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (part < parts.length - 1)
|
|
596
|
+
insideAt.push(state.kind === "single" ? "single" : state.kind === "dollar" ? "dollar" : undefined);
|
|
597
|
+
});
|
|
598
|
+
if (!insideAt.some(Boolean))
|
|
599
|
+
return { parts: [...parts], values: [...values], original: values.map((_, i) => i), inlined: [], refs: [] };
|
|
600
|
+
const out = [parts[0]];
|
|
601
|
+
const outValues = [];
|
|
602
|
+
const original = [];
|
|
603
|
+
const inlined = [];
|
|
604
|
+
const refs = [];
|
|
605
|
+
values.forEach((v, i) => {
|
|
606
|
+
if (!insideAt[i]) {
|
|
607
|
+
outValues.push(v);
|
|
608
|
+
original.push(i);
|
|
609
|
+
out.push(parts[i + 1]);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
let text;
|
|
613
|
+
if (isPostgresObject(v) || isColumnRef(v)) {
|
|
614
|
+
text = renderReference(v);
|
|
615
|
+
refs.push(v);
|
|
616
|
+
}
|
|
617
|
+
else if (v instanceof SqlLiteral)
|
|
618
|
+
text = v.sql;
|
|
619
|
+
else if (v === null)
|
|
620
|
+
text = "NULL";
|
|
621
|
+
else if (typeof v === "string" || typeof v === "number" || typeof v === "bigint" || typeof v === "boolean")
|
|
622
|
+
text = String(v);
|
|
623
|
+
else
|
|
624
|
+
throw new SqlTemplateError("func", `the interpolation inside the body is ${describe(v)}; a body takes an object, a column reference, a string or a number`, i, parts[i].length);
|
|
625
|
+
// Inside a '...' string a quote is doubled.
|
|
626
|
+
out[out.length - 1] += (insideAt[i] === "single" ? text.replace(/'/g, "''") : text) + parts[i + 1];
|
|
627
|
+
inlined.push(i);
|
|
628
|
+
});
|
|
629
|
+
return { parts: out, values: outValues, original, inlined, refs };
|
|
630
|
+
}
|
|
631
|
+
/** A role named where one is granted to: an interpolated role entity gives its name. */
|
|
632
|
+
function roleName(ctx, r) {
|
|
633
|
+
if (r.name)
|
|
634
|
+
return r.name;
|
|
635
|
+
const t = ctx.tokens[r.span.from];
|
|
636
|
+
const v = t?.kind === "ref" ? ctx.values[t.part] : undefined;
|
|
637
|
+
if (isPostgresObject(v))
|
|
638
|
+
return v.props.name;
|
|
639
|
+
return identValue(req(text(ctx, r.span)));
|
|
640
|
+
}
|
|
641
|
+
/** A GRANT's, REVOKE's or ALTER DEFAULT PRIVILEGES's props. */
|
|
642
|
+
function grantProps(ctx, node, ddl, source) {
|
|
643
|
+
const action = node.revoke ? "revoke" : "grant";
|
|
644
|
+
const grantees = node.grantees.map((g) => roleName(ctx, g));
|
|
645
|
+
if (node.defaults) {
|
|
646
|
+
if (node.privileges.some((p) => p.columns.length > 0))
|
|
647
|
+
throw new SqlTemplateError("grant", "default privileges are on whole objects; a column list is for a grant on one table", 0, 0);
|
|
648
|
+
const on = (node.on === "ROUTINES" ? "functions" : node.on.toLowerCase());
|
|
649
|
+
const forRoles = node.forRoles?.map((r) => roleName(ctx, r));
|
|
650
|
+
const inSchemas = node.inSchemas?.map((s) => columnName(ctx, s));
|
|
651
|
+
if (on === "schemas" && inSchemas)
|
|
652
|
+
throw new SqlTemplateError("grant", "default privileges on schemas cannot be limited to a schema (IN SCHEMA)", 0, 0);
|
|
653
|
+
const privileges = node.privileges.map((p) => p.privilege.toLowerCase());
|
|
654
|
+
return stripUnset({
|
|
655
|
+
name: `default privileges ${action === "grant" ? "grant" : "revoke"} ${privileges.join(", ")} on ${on}${inSchemas ? ` in ${inSchemas.join(", ")}` : ""}${forRoles ? ` for ${forRoles.join(", ")}` : ""} ${action === "grant" ? "to" : "from"} ${grantees.join(", ")}`,
|
|
656
|
+
action,
|
|
657
|
+
grantOptionFor: node.grantOptionFor || undefined,
|
|
658
|
+
forRoles,
|
|
659
|
+
inSchemas,
|
|
660
|
+
privileges,
|
|
661
|
+
on,
|
|
662
|
+
grantees,
|
|
663
|
+
withGrantOption: node.withGrantOption || undefined,
|
|
664
|
+
ddl,
|
|
665
|
+
source,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
const on = (node.on === "TABLE" ? "table" : node.on.toLowerCase());
|
|
669
|
+
const routine = on === "function" || on === "procedure" || on === "routine";
|
|
670
|
+
const objects = [];
|
|
671
|
+
const objectNames = [];
|
|
672
|
+
for (const o of node.objects) {
|
|
673
|
+
const t = target(ctx, o.name);
|
|
674
|
+
objects.push(t);
|
|
675
|
+
let sqlName = typeof t === "string" ? t : on === "schema" ? t.props.name : t.sqlName;
|
|
676
|
+
if (routine) {
|
|
677
|
+
if (o.args)
|
|
678
|
+
sqlName += `(${req(text(ctx, o.args, "objects")) ?? ""})`;
|
|
679
|
+
else if (typeof t !== "string" && (t.entityType === POSTGRES_ENTITY_TYPES.function || t.entityType === POSTGRES_ENTITY_TYPES.procedure)) {
|
|
680
|
+
// An interpolated routine is named by its own parameter types.
|
|
681
|
+
sqlName += `(${routineArgTypes(t.props)})`;
|
|
682
|
+
}
|
|
683
|
+
else
|
|
684
|
+
throw new SqlTemplateError("grant", `${sqlName}: name the parameter types (${sqlName}(...)), or interpolate the routine, since overloads share a name`, 0, 0);
|
|
685
|
+
}
|
|
686
|
+
objectNames.push(sqlName);
|
|
687
|
+
}
|
|
688
|
+
const privileges = node.privileges.map((p) => stripUnset({ privilege: p.privilege.toLowerCase(), columns: p.columns.length > 0 ? p.columns.map((c) => columnName(ctx, c)) : undefined }));
|
|
689
|
+
if (on !== "table" && privileges.some((p) => p.columns))
|
|
690
|
+
throw new SqlTemplateError("grant", "a column list is for privileges on a table", 0, 0);
|
|
691
|
+
const verb = action === "grant" ? "to" : "from";
|
|
692
|
+
return stripUnset({
|
|
693
|
+
name: `${action} ${privileges.map((p) => `${p.privilege}${p.columns ? `(${p.columns.join(", ")})` : ""}`).join(", ")} on ${on} ${objectNames.join(", ")} ${verb} ${grantees.join(", ")}`,
|
|
694
|
+
action,
|
|
695
|
+
grantOptionFor: node.grantOptionFor || undefined,
|
|
696
|
+
privileges,
|
|
697
|
+
on,
|
|
698
|
+
objects,
|
|
699
|
+
objectNames,
|
|
700
|
+
grantees,
|
|
701
|
+
withGrantOption: node.withGrantOption || undefined,
|
|
702
|
+
ddl,
|
|
703
|
+
source,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
/** A routine's parameter types, as `GRANT ... ON FUNCTION f(...)` names it: the `OUT` ones left out. */
|
|
707
|
+
const routineArgTypes = (props) => props.args.filter((a) => a.mode !== "out").map((a) => a.type).join(", ");
|
|
708
|
+
/** The interpolated functions and procedures not followed by their own `(...)`: the ones a grant's statement writes with the parameter types (#3707). */
|
|
709
|
+
function bareRoutineSplices(tokens, values) {
|
|
710
|
+
const out = new Set();
|
|
711
|
+
tokens.forEach((t, k) => {
|
|
712
|
+
if (t.kind !== "ref")
|
|
713
|
+
return;
|
|
714
|
+
const v = values[t.part];
|
|
715
|
+
if (!isPostgresObject(v) || (v.entityType !== POSTGRES_ENTITY_TYPES.function && v.entityType !== POSTGRES_ENTITY_TYPES.procedure))
|
|
716
|
+
return;
|
|
717
|
+
const next = tokens.slice(k + 1).find((x) => !isTrivia(x));
|
|
718
|
+
if (!(next?.kind === "punct" && next.text === "("))
|
|
719
|
+
out.add(t.part);
|
|
720
|
+
});
|
|
721
|
+
return out;
|
|
722
|
+
}
|
|
723
|
+
function build(tag, strings, rawValues) {
|
|
724
|
+
const rawParts = templateParts(strings);
|
|
725
|
+
const routine = tag === "func" || tag === "procedure";
|
|
726
|
+
const joined = routine ? inlineQuoted(rawParts, rawValues) : undefined;
|
|
727
|
+
const parts = joined ? joined.parts : rawParts;
|
|
728
|
+
const values = joined ? joined.values : rawValues;
|
|
729
|
+
const tokens = spliceWith(POSTGRES_TEMPLATES, tag, parts, values);
|
|
730
|
+
let nodes;
|
|
731
|
+
try {
|
|
732
|
+
nodes = parseStatements(tokens);
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
if (!(err instanceof SqlSyntaxError))
|
|
736
|
+
throw err;
|
|
737
|
+
throw templateSyntaxError(tag, parts, err);
|
|
738
|
+
}
|
|
739
|
+
const [node, ...rest] = nodes;
|
|
740
|
+
if (!STATEMENTS_OF[tag].includes(node.statement)) {
|
|
741
|
+
const holds = STATEMENT_NAMES[node.statement];
|
|
742
|
+
const use = TAG_OF[node.statement];
|
|
743
|
+
throw new SqlTemplateError(tag, use
|
|
744
|
+
? `holds a ${holds}; use the ${use} tag`
|
|
745
|
+
: node.statement === "rowSecurity"
|
|
746
|
+
? "holds an ALTER TABLE ... ROW LEVEL SECURITY before any CREATE; it goes after the table's CREATE TABLE, in the table's template"
|
|
747
|
+
: `holds a ${holds} before any CREATE; a COMMENT ON goes after the CREATE it comments on`, 0, 0);
|
|
748
|
+
}
|
|
749
|
+
const stray = followOnStray(tag, rest);
|
|
750
|
+
if (stray) {
|
|
751
|
+
throw new SqlTemplateError(tag, `holds a second statement (${STATEMENT_NAMES[stray.statement]}); a template declares one object, followed only by COMMENT ON statements for it${tag === "table" ? " and ALTER TABLE ... ROW LEVEL SECURITY" : ""}`, 0, 0);
|
|
752
|
+
}
|
|
753
|
+
const comments = rest.filter((n) => n.statement === "comment");
|
|
754
|
+
const rowSecurity = rest.filter((n) => n.statement === "rowSecurity");
|
|
755
|
+
const positions = regclassPositions(tokens, values);
|
|
756
|
+
const bareRoutines = tag === "grant" ? bareRoutineSplices(tokens, values) : new Set();
|
|
757
|
+
const render = (v, i) => {
|
|
758
|
+
const at = i === undefined ? undefined : positions.get(i);
|
|
759
|
+
if (at && isPostgresObject(v))
|
|
760
|
+
return at === "call" ? `${regclassText(v.sqlName)}::regclass` : regclassText(v.sqlName);
|
|
761
|
+
// #3707: a routine a grant interpolates is written with its parameter types, so the statement names it on its own.
|
|
762
|
+
if (i !== undefined && bareRoutines.has(i) && isPostgresObject(v))
|
|
763
|
+
return `${renderReference(v)}(${routineArgTypes(v.props)})`;
|
|
764
|
+
return renderReference(v);
|
|
765
|
+
};
|
|
766
|
+
const ctx = { d: { ...POSTGRES_TEMPLATES, renderReference: render }, tokens, values, fed: values.map(() => new Set()) };
|
|
767
|
+
for (const t of tokens)
|
|
768
|
+
if (t.splice !== undefined)
|
|
769
|
+
ctx.fed[t.splice].add("ddl");
|
|
770
|
+
const ddl = untokenize(tokens, (i) => render(values[i], i)).trim().replace(/;\s*$/, "");
|
|
771
|
+
const source = { strings: rawParts };
|
|
772
|
+
const dependsOn = [...new Set([...values, ...(joined?.refs ?? [])].filter((v) => isPostgresObject(v) || isColumnRef(v)))];
|
|
773
|
+
const slots = [];
|
|
774
|
+
const unparsed = unparsedClauses(ctx, nodes.flatMap((n) => ("unknown" in n && n.unknown ? n.unknown : [])));
|
|
775
|
+
const make = (type, q, props, columnNames, sqlName = sqlNameOf(q)) => {
|
|
776
|
+
if (unparsed)
|
|
777
|
+
props.unparsed = unparsed;
|
|
778
|
+
const entity = makeSqlEntity(PostgresObject.prototype, type, sqlName, props, columnNames, dependsOn);
|
|
779
|
+
const fed = ctx.fed.map((paths) => [...paths].sort());
|
|
780
|
+
if (!joined)
|
|
781
|
+
setInterpolationFields(entity, fed);
|
|
782
|
+
else {
|
|
783
|
+
// Back to the template's own interpolations: one inlined into the body feeds the body.
|
|
784
|
+
const byOriginal = rawValues.map(() => ["body", "ddl"]);
|
|
785
|
+
joined.original.forEach((orig, i) => (byOriginal[orig] = fed[i]));
|
|
786
|
+
setInterpolationFields(entity, byOriginal);
|
|
787
|
+
}
|
|
788
|
+
setTypedSlots(entity, slots);
|
|
789
|
+
return entity;
|
|
790
|
+
};
|
|
791
|
+
switch (node.statement) {
|
|
792
|
+
case "schema": {
|
|
793
|
+
const q = node.name ? qualified(ctx, node.name) : { name: identValue(req(text(ctx, node.authorization))) };
|
|
794
|
+
if (q.schema !== undefined)
|
|
795
|
+
throw new SqlTemplateError(tag, `a schema name is not qualified (${sqlNameOf(q)})`, 0, 0);
|
|
796
|
+
const comment = applyComments(tag, ctx, comments, q, ["SCHEMA"]);
|
|
797
|
+
const props = stripUnset({ name: q.name, ifNotExists: node.ifNotExists, authorization: text(ctx, node.authorization, "authorization"), comment, ddl, source });
|
|
798
|
+
return make(POSTGRES_ENTITY_TYPES.schema, q, props);
|
|
799
|
+
}
|
|
800
|
+
case "extension": {
|
|
801
|
+
const q = qualified(ctx, node.name);
|
|
802
|
+
const comment = applyComments(tag, ctx, comments, { name: q.name }, ["EXTENSION"]);
|
|
803
|
+
const schemaRef = node.schema ? text(ctx, node.schema, "schema") : undefined;
|
|
804
|
+
const props = stripUnset({
|
|
805
|
+
name: q.name,
|
|
806
|
+
schema: schemaRef === undefined ? undefined : identValue(schemaRef),
|
|
807
|
+
version: stringValue(text(ctx, node.version, "version")),
|
|
808
|
+
cascade: node.cascade,
|
|
809
|
+
ifNotExists: node.ifNotExists,
|
|
810
|
+
comment,
|
|
811
|
+
ddl,
|
|
812
|
+
source,
|
|
813
|
+
});
|
|
814
|
+
return make(POSTGRES_ENTITY_TYPES.extension, { name: q.name }, props);
|
|
815
|
+
}
|
|
816
|
+
case "enum": {
|
|
817
|
+
const q = qualified(ctx, node.name);
|
|
818
|
+
const comment = applyComments(tag, ctx, comments, q, ["TYPE"]);
|
|
819
|
+
const props = stripUnset({ ...q, labels: node.labels.map((l) => req(stringValue(text(ctx, l, "labels")))), comment, ddl, source });
|
|
820
|
+
return make(POSTGRES_ENTITY_TYPES.enum, q, props);
|
|
821
|
+
}
|
|
822
|
+
case "composite": {
|
|
823
|
+
const q = qualified(ctx, node.name);
|
|
824
|
+
const comment = applyComments(tag, ctx, comments, q, ["TYPE"]);
|
|
825
|
+
const attributes = node.attributes.map((a) => {
|
|
826
|
+
feed(ctx, a.nameSpan, "attributes");
|
|
827
|
+
return stripUnset({ name: columnName(ctx, { name: a.name, span: a.nameSpan }), type: req(text(ctx, a.type, "attributes")), collate: text(ctx, a.collate, "attributes") });
|
|
828
|
+
});
|
|
829
|
+
const props = strip({ ...q, attributes, comment, ddl, source }, ["attributes"]);
|
|
830
|
+
return make(POSTGRES_ENTITY_TYPES.composite, q, props);
|
|
831
|
+
}
|
|
832
|
+
case "range": {
|
|
833
|
+
const q = qualified(ctx, node.name);
|
|
834
|
+
const comment = applyComments(tag, ctx, comments, q, ["TYPE"]);
|
|
835
|
+
const opt = definitionValues(ctx, node.options, "options");
|
|
836
|
+
const props = stripUnset({
|
|
837
|
+
...q,
|
|
838
|
+
subtype: req(opt.subtype),
|
|
839
|
+
subtypeOpclass: opt.subtype_opclass,
|
|
840
|
+
collation: opt.collation,
|
|
841
|
+
canonical: opt.canonical,
|
|
842
|
+
subtypeDiff: opt.subtype_diff,
|
|
843
|
+
multirangeTypeName: opt.multirange_type_name,
|
|
844
|
+
comment,
|
|
845
|
+
ddl,
|
|
846
|
+
source,
|
|
847
|
+
});
|
|
848
|
+
return make(POSTGRES_ENTITY_TYPES.range, q, props);
|
|
849
|
+
}
|
|
850
|
+
case "aggregate": {
|
|
851
|
+
const q = qualified(ctx, node.name);
|
|
852
|
+
const comment = applyComments(tag, ctx, comments, q, ["AGGREGATE"]);
|
|
853
|
+
const arg = (a) => stripUnset({ mode: (a.mode?.toLowerCase() ?? "in"), name: a.name, type: req(text(ctx, a.type, "args")) });
|
|
854
|
+
const props = strip({
|
|
855
|
+
...q,
|
|
856
|
+
orReplace: node.orReplace || undefined,
|
|
857
|
+
args: node.args.map(arg),
|
|
858
|
+
orderBy: node.orderBy?.map(arg),
|
|
859
|
+
options: definitionValues(ctx, node.options, "options"),
|
|
860
|
+
comment,
|
|
861
|
+
ddl,
|
|
862
|
+
source,
|
|
863
|
+
}, ["args"]);
|
|
864
|
+
return make(POSTGRES_ENTITY_TYPES.aggregate, q, props);
|
|
865
|
+
}
|
|
866
|
+
case "collation": {
|
|
867
|
+
const q = qualified(ctx, node.name);
|
|
868
|
+
const comment = applyComments(tag, ctx, comments, q, ["COLLATION"]);
|
|
869
|
+
const props = stripUnset({ ...q, ifNotExists: node.ifNotExists || undefined, options: definitionValues(ctx, node.options, "options"), comment, ddl, source });
|
|
870
|
+
return make(POSTGRES_ENTITY_TYPES.collation, q, props);
|
|
871
|
+
}
|
|
872
|
+
case "cast": {
|
|
873
|
+
const sourceType = req(text(ctx, node.source, "sourceType"));
|
|
874
|
+
const targetType = req(text(ctx, node.target, "targetType"));
|
|
875
|
+
const name = `(${sourceType} AS ${targetType})`;
|
|
876
|
+
const comment = applyComments(tag, ctx, comments, { name }, ["CAST"]);
|
|
877
|
+
const props = stripUnset({
|
|
878
|
+
name,
|
|
879
|
+
sourceType,
|
|
880
|
+
targetType,
|
|
881
|
+
method: node.method,
|
|
882
|
+
function: node.function ? target(ctx, node.function) : undefined,
|
|
883
|
+
functionName: node.function ? targetName(ctx, node.function) : undefined,
|
|
884
|
+
functionArgs: node.functionArgs ? text(ctx, node.functionArgs, "functionArgs") : undefined,
|
|
885
|
+
context: node.context,
|
|
886
|
+
comment,
|
|
887
|
+
ddl,
|
|
888
|
+
source,
|
|
889
|
+
});
|
|
890
|
+
return make(POSTGRES_ENTITY_TYPES.cast, { name }, props, undefined, name);
|
|
891
|
+
}
|
|
892
|
+
case "eventTrigger": {
|
|
893
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
894
|
+
feed(ctx, node.name.span, "name");
|
|
895
|
+
const comment = applyComments(tag, ctx, comments, { name }, ["EVENT TRIGGER"]);
|
|
896
|
+
const tags = node.filters.flatMap((f) => f.values.map((v) => req(stringValue(text(ctx, v, "tags")))));
|
|
897
|
+
const props = strip({
|
|
898
|
+
name,
|
|
899
|
+
event: node.event.name || identValue(req(text(ctx, node.event.span, "event"))),
|
|
900
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
901
|
+
function: target(ctx, node.function),
|
|
902
|
+
functionName: targetName(ctx, node.function),
|
|
903
|
+
comment,
|
|
904
|
+
ddl,
|
|
905
|
+
source,
|
|
906
|
+
}, []);
|
|
907
|
+
return make(POSTGRES_ENTITY_TYPES.eventTrigger, { name }, props);
|
|
908
|
+
}
|
|
909
|
+
case "domain": {
|
|
910
|
+
const q = qualified(ctx, node.name);
|
|
911
|
+
const checks = node.checks.map((c) => stripUnset({ name: c.name, expr: req(text(ctx, c.expr, "checks")), notValid: c.notValid }));
|
|
912
|
+
const constraints = new Map(checks.filter((c) => c.name !== undefined).map((c) => [c.name, c]));
|
|
913
|
+
const comment = applyComments(tag, ctx, comments.map((c) => (c.objectType === "DOMAIN CONSTRAINT" ? { ...c, objectType: "CONSTRAINT" } : c)), q, ["DOMAIN", "TYPE"], { constraints });
|
|
914
|
+
const props = strip({
|
|
915
|
+
...q,
|
|
916
|
+
dataType: req(text(ctx, node.type, "dataType")),
|
|
917
|
+
collate: text(ctx, node.collate, "collate"),
|
|
918
|
+
default: text(ctx, node.default, "default"),
|
|
919
|
+
notNull: node.notNull,
|
|
920
|
+
checks,
|
|
921
|
+
comment,
|
|
922
|
+
ddl,
|
|
923
|
+
source,
|
|
924
|
+
}, ["checks"]);
|
|
925
|
+
return make(POSTGRES_ENTITY_TYPES.domain, q, props);
|
|
926
|
+
}
|
|
927
|
+
case "sequence": {
|
|
928
|
+
const q = qualified(ctx, node.name);
|
|
929
|
+
const comment = applyComments(tag, ctx, comments, q, ["SEQUENCE"]);
|
|
930
|
+
const opt = {};
|
|
931
|
+
for (const o of node.options) {
|
|
932
|
+
const v = text(ctx, o.value, "options");
|
|
933
|
+
switch (o.option) {
|
|
934
|
+
case "AS":
|
|
935
|
+
opt.dataType = v;
|
|
936
|
+
break;
|
|
937
|
+
case "INCREMENT":
|
|
938
|
+
opt.increment = v;
|
|
939
|
+
break;
|
|
940
|
+
case "MINVALUE":
|
|
941
|
+
opt.minValue = v;
|
|
942
|
+
break;
|
|
943
|
+
case "MAXVALUE":
|
|
944
|
+
opt.maxValue = v;
|
|
945
|
+
break;
|
|
946
|
+
case "NO MINVALUE":
|
|
947
|
+
opt.minValue = null;
|
|
948
|
+
break;
|
|
949
|
+
case "NO MAXVALUE":
|
|
950
|
+
opt.maxValue = null;
|
|
951
|
+
break;
|
|
952
|
+
case "START":
|
|
953
|
+
opt.start = v;
|
|
954
|
+
break;
|
|
955
|
+
case "CACHE":
|
|
956
|
+
opt.cache = v;
|
|
957
|
+
break;
|
|
958
|
+
case "CYCLE":
|
|
959
|
+
opt.cycle = true;
|
|
960
|
+
break;
|
|
961
|
+
case "NO CYCLE":
|
|
962
|
+
opt.cycle = false;
|
|
963
|
+
break;
|
|
964
|
+
case "OWNED BY":
|
|
965
|
+
opt.ownedBy = v;
|
|
966
|
+
break;
|
|
967
|
+
case "LOGGED":
|
|
968
|
+
case "UNLOGGED":
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const props = { ...stripUnset({ ...q, persistence: node.persistence, ifNotExists: node.ifNotExists }), ...opt, ...stripUnset({ comment, ddl, source }) };
|
|
973
|
+
if (opt.cycle === false)
|
|
974
|
+
props.cycle = false;
|
|
975
|
+
return make(POSTGRES_ENTITY_TYPES.sequence, q, props);
|
|
976
|
+
}
|
|
977
|
+
case "table": {
|
|
978
|
+
const q = qualified(ctx, node.name);
|
|
979
|
+
const columns = node.columns.map((c) => columnDef(ctx, c));
|
|
980
|
+
let primaryKey;
|
|
981
|
+
const uniques = [];
|
|
982
|
+
const checks = [];
|
|
983
|
+
const foreignKeys = [];
|
|
984
|
+
const exclusions = [];
|
|
985
|
+
const file = (c, cols) => {
|
|
986
|
+
if (c.kind === "FOREIGN KEY") {
|
|
987
|
+
foreignKeys.push(foreignKeyDef(ctx, c, cols));
|
|
988
|
+
slots.push(...referenceSlots(ctx, c, cols));
|
|
989
|
+
}
|
|
990
|
+
else if (c.kind === "PRIMARY KEY") {
|
|
991
|
+
if (primaryKey)
|
|
992
|
+
throw new SqlTemplateError(tag, `${sqlNameOf(q)} declares more than one primary key`, 0, 0);
|
|
993
|
+
primaryKey = keyDef(ctx, c, cols, "primaryKey");
|
|
994
|
+
}
|
|
995
|
+
else if (c.kind === "UNIQUE")
|
|
996
|
+
uniques.push(keyDef(ctx, c, cols, "uniques"));
|
|
997
|
+
else if (c.kind === "CHECK")
|
|
998
|
+
checks.push(checkDef(ctx, c));
|
|
999
|
+
else if (c.kind === "EXCLUDE")
|
|
1000
|
+
exclusions.push(exclusionDef(ctx, c));
|
|
1001
|
+
else if (c.kind === "NOT NULL") {
|
|
1002
|
+
const col = columns.find((x) => x.name === cols[0]);
|
|
1003
|
+
if (!col)
|
|
1004
|
+
throw new SqlTemplateError(tag, `NOT NULL ${cols[0]} names no column of ${sqlNameOf(q)}`, 0, 0);
|
|
1005
|
+
col.notNull = true;
|
|
1006
|
+
if (c.name)
|
|
1007
|
+
col.notNullName = c.name;
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
node.columns.forEach((c, i) => c.constraints.forEach((k) => file(k, [columns[i].name])));
|
|
1011
|
+
node.constraints.forEach((k) => {
|
|
1012
|
+
// Text spliced into a key's column list feeds the key's columns.
|
|
1013
|
+
const path = k.kind === "PRIMARY KEY" ? "primaryKey.columns" : k.kind === "UNIQUE" ? "uniques" : k.kind === "FOREIGN KEY" ? "foreignKeys" : undefined;
|
|
1014
|
+
if (path)
|
|
1015
|
+
for (const x of k.columns)
|
|
1016
|
+
feed(ctx, x.span, path);
|
|
1017
|
+
file(k, k.columns.map((x) => columnName(ctx, x)));
|
|
1018
|
+
});
|
|
1019
|
+
const named = new Map();
|
|
1020
|
+
for (const c of [primaryKey, ...uniques, ...checks, ...foreignKeys, ...exclusions])
|
|
1021
|
+
if (c?.name)
|
|
1022
|
+
named.set(c.name, c);
|
|
1023
|
+
const comment = applyComments(tag, ctx, comments, q, ["TABLE"], { columns: new Map(columns.map((c) => [c.name, c])), constraints: named });
|
|
1024
|
+
let rls;
|
|
1025
|
+
let force;
|
|
1026
|
+
for (const r of rowSecurity) {
|
|
1027
|
+
const t = qualified(ctx, r.table);
|
|
1028
|
+
if (t.name !== q.name || (t.schema !== undefined && q.schema !== undefined && t.schema !== q.schema)) {
|
|
1029
|
+
throw new SqlTemplateError(tag, `ALTER TABLE ${sqlNameOf(t)} ... ROW LEVEL SECURITY names another table than ${sqlNameOf(q)}`, 0, 0);
|
|
1030
|
+
}
|
|
1031
|
+
if (r.action === "ENABLE" || r.action === "DISABLE")
|
|
1032
|
+
rls = r.action === "ENABLE";
|
|
1033
|
+
else
|
|
1034
|
+
force = r.action === "FORCE";
|
|
1035
|
+
}
|
|
1036
|
+
const props = strip({
|
|
1037
|
+
...q,
|
|
1038
|
+
persistence: node.persistence,
|
|
1039
|
+
ifNotExists: node.ifNotExists,
|
|
1040
|
+
partitionOf: node.partitionOf ? target(ctx, node.partitionOf) : undefined,
|
|
1041
|
+
partitionBound: text(ctx, node.partitionBound, "partitionBound"),
|
|
1042
|
+
ofType: node.ofType ? target(ctx, node.ofType) : undefined,
|
|
1043
|
+
columns,
|
|
1044
|
+
primaryKey,
|
|
1045
|
+
uniques,
|
|
1046
|
+
checks,
|
|
1047
|
+
foreignKeys,
|
|
1048
|
+
exclusions,
|
|
1049
|
+
like: node.like.map((l) => req(text(ctx, l, "like"))),
|
|
1050
|
+
inherits: (node.inherits ?? []).map((n) => target(ctx, n)),
|
|
1051
|
+
partitionBy: text(ctx, node.partitionBy, "partitionBy"),
|
|
1052
|
+
using: text(ctx, node.using, "using"),
|
|
1053
|
+
with: text(ctx, node.with, "with"),
|
|
1054
|
+
onCommit: text(ctx, node.onCommit),
|
|
1055
|
+
tablespace: text(ctx, node.tablespace, "tablespace"),
|
|
1056
|
+
rowSecurity: rls,
|
|
1057
|
+
forceRowSecurity: force,
|
|
1058
|
+
comment,
|
|
1059
|
+
ddl,
|
|
1060
|
+
source,
|
|
1061
|
+
}, ["columns", "uniques", "checks", "foreignKeys", "exclusions", "like", "inherits"]);
|
|
1062
|
+
if (rls === false)
|
|
1063
|
+
props.rowSecurity = false;
|
|
1064
|
+
if (force === false)
|
|
1065
|
+
props.forceRowSecurity = false;
|
|
1066
|
+
return make(POSTGRES_ENTITY_TYPES.table, q, props, columns.map((c) => c.name));
|
|
1067
|
+
}
|
|
1068
|
+
case "index": {
|
|
1069
|
+
if (!node.name) {
|
|
1070
|
+
throw new SqlTemplateError(tag, "an index needs a name: the name is its identity on the server, and an unnamed index gets a generated one", 0, 0);
|
|
1071
|
+
}
|
|
1072
|
+
const tableTarget = target(ctx, node.table);
|
|
1073
|
+
const tq = qualified(ctx, node.table);
|
|
1074
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1075
|
+
feed(ctx, node.name.span, "name");
|
|
1076
|
+
// An index lives in its table's schema.
|
|
1077
|
+
const q = { schema: tq.schema, name };
|
|
1078
|
+
const comment = applyComments(tag, ctx, comments, q, ["INDEX"]);
|
|
1079
|
+
const props = strip({
|
|
1080
|
+
...q,
|
|
1081
|
+
table: tableTarget,
|
|
1082
|
+
tableName: sqlNameOf(tq),
|
|
1083
|
+
unique: node.unique,
|
|
1084
|
+
concurrently: node.concurrently,
|
|
1085
|
+
ifNotExists: node.ifNotExists,
|
|
1086
|
+
only: node.only,
|
|
1087
|
+
method: node.using,
|
|
1088
|
+
elements: node.elements.map((e) => {
|
|
1089
|
+
const sig = tokens.slice(e.span.from, e.span.to).filter((t) => !isTrivia(t));
|
|
1090
|
+
const ref = sig[0]?.kind === "ref" ? values[sig[0].part] : undefined;
|
|
1091
|
+
const byRef = isColumnRef(ref) && sig.slice(1).every((t) => t.kind === "ident" || t.kind === "qident") ? ref.attribute : undefined;
|
|
1092
|
+
return stripUnset({ expr: req(text(ctx, e.span, "elements")), column: e.column ?? byRef });
|
|
1093
|
+
}),
|
|
1094
|
+
include: text(ctx, node.include, "include"),
|
|
1095
|
+
nullsNotDistinct: node.nullsNotDistinct,
|
|
1096
|
+
with: text(ctx, node.with, "with"),
|
|
1097
|
+
tablespace: text(ctx, node.tablespace, "tablespace"),
|
|
1098
|
+
where: text(ctx, node.where, "where"),
|
|
1099
|
+
comment,
|
|
1100
|
+
ddl,
|
|
1101
|
+
source,
|
|
1102
|
+
}, ["elements"]);
|
|
1103
|
+
if (node.nullsNotDistinct === false)
|
|
1104
|
+
props.nullsNotDistinct = false;
|
|
1105
|
+
return make(POSTGRES_ENTITY_TYPES.index, q, props);
|
|
1106
|
+
}
|
|
1107
|
+
case "view": {
|
|
1108
|
+
const q = qualified(ctx, node.name);
|
|
1109
|
+
const lin = lineage(ctx, node.query);
|
|
1110
|
+
const declared = node.columnNames.map((c) => columnName(ctx, c));
|
|
1111
|
+
// A declared column list renames the outputs, by position.
|
|
1112
|
+
const edges = lin.edges.map((e, i) => ({ ...e, output: declared[i] ?? e.output }));
|
|
1113
|
+
const outputs = declared.length > 0 ? declared : edges.map((e) => e.output);
|
|
1114
|
+
const columnComments = {};
|
|
1115
|
+
const comment = applyComments(tag, ctx, comments, q, node.materialized ? ["MATERIALIZED VIEW"] : ["VIEW"], { columnComments });
|
|
1116
|
+
const props = strip({
|
|
1117
|
+
...q,
|
|
1118
|
+
orReplace: node.orReplace,
|
|
1119
|
+
recursive: node.recursive,
|
|
1120
|
+
temporary: node.temporary,
|
|
1121
|
+
ifNotExists: node.ifNotExists,
|
|
1122
|
+
columns: declared,
|
|
1123
|
+
with: text(ctx, node.with, "with"),
|
|
1124
|
+
using: text(ctx, node.using, "using"),
|
|
1125
|
+
tablespace: text(ctx, node.tablespace, "tablespace"),
|
|
1126
|
+
query: req(text(ctx, node.query, "query")),
|
|
1127
|
+
checkOption: text(ctx, node.checkOption, "checkOption"),
|
|
1128
|
+
withData: node.withData,
|
|
1129
|
+
reads: lin.reads,
|
|
1130
|
+
lineage: edges,
|
|
1131
|
+
columnComments: Object.keys(columnComments).length > 0 ? columnComments : undefined,
|
|
1132
|
+
comment,
|
|
1133
|
+
ddl,
|
|
1134
|
+
source,
|
|
1135
|
+
}, ["columns", "reads", "lineage"]);
|
|
1136
|
+
if (node.withData === false)
|
|
1137
|
+
props.withData = false;
|
|
1138
|
+
feed(ctx, node.query, "lineage");
|
|
1139
|
+
return make(node.materialized ? POSTGRES_ENTITY_TYPES.materializedView : POSTGRES_ENTITY_TYPES.view, q, props, outputs);
|
|
1140
|
+
}
|
|
1141
|
+
case "function":
|
|
1142
|
+
case "procedure": {
|
|
1143
|
+
const q = qualified(ctx, node.name);
|
|
1144
|
+
const comment = applyComments(tag, ctx, comments, q, node.statement === "function" ? ["FUNCTION", "ROUTINE"] : ["PROCEDURE", "ROUTINE"]);
|
|
1145
|
+
const flag = (o) => node.options.find((x) => x.option === o)?.flag;
|
|
1146
|
+
const value = (o, path) => {
|
|
1147
|
+
const x = node.options.find((y) => y.option === o);
|
|
1148
|
+
return x?.value ? text(ctx, x.value, path) : undefined;
|
|
1149
|
+
};
|
|
1150
|
+
const set = {};
|
|
1151
|
+
for (const s of node.set)
|
|
1152
|
+
set[s.name] = req(text(ctx, s.value, "set"));
|
|
1153
|
+
const [def, link] = node.body.map((b) => stringValue(text(ctx, b, "body")));
|
|
1154
|
+
const lang = text(ctx, node.language, "language");
|
|
1155
|
+
const props = strip({
|
|
1156
|
+
...q,
|
|
1157
|
+
orReplace: node.orReplace || undefined,
|
|
1158
|
+
args: node.args.map((a) => stripUnset({
|
|
1159
|
+
mode: (a.mode?.toLowerCase() ?? "in"),
|
|
1160
|
+
name: a.name,
|
|
1161
|
+
type: req(text(ctx, a.type, "args")),
|
|
1162
|
+
default: text(ctx, a.default, "args"),
|
|
1163
|
+
})),
|
|
1164
|
+
returns: node.returns ? `${node.returns.setof ? "SETOF " : ""}${req(text(ctx, node.returns.type, "returns"))}` : undefined,
|
|
1165
|
+
returnsTable: node.returnsTable?.map((c) => ({ name: c.name, type: req(text(ctx, c.type, "returns")) })),
|
|
1166
|
+
language: lang === undefined ? undefined : lang.startsWith("'") ? stringValue(lang) : identValue(lang),
|
|
1167
|
+
volatility: value("VOLATILITY", "volatility")?.toLowerCase(),
|
|
1168
|
+
strict: flag("STRICT"),
|
|
1169
|
+
securityDefiner: flag("SECURITY"),
|
|
1170
|
+
leakproof: flag("LEAKPROOF"),
|
|
1171
|
+
parallel: value("PARALLEL", "parallel")?.toLowerCase(),
|
|
1172
|
+
cost: value("COST", "cost"),
|
|
1173
|
+
rows: value("ROWS", "rows"),
|
|
1174
|
+
support: value("SUPPORT", "support"),
|
|
1175
|
+
window: flag("WINDOW"),
|
|
1176
|
+
transform: value("TRANSFORM", "transform"),
|
|
1177
|
+
set: Object.keys(set).length > 0 ? set : undefined,
|
|
1178
|
+
body: req(def),
|
|
1179
|
+
link,
|
|
1180
|
+
reads: (joined?.refs ?? []).filter((v) => isPostgresObject(v)),
|
|
1181
|
+
comment,
|
|
1182
|
+
ddl,
|
|
1183
|
+
source,
|
|
1184
|
+
}, ["args", "reads"]);
|
|
1185
|
+
for (const k of ["strict", "securityDefiner", "leakproof"])
|
|
1186
|
+
if (flag(k === "securityDefiner" ? "SECURITY" : k.toUpperCase()) === false)
|
|
1187
|
+
props[k] = false;
|
|
1188
|
+
return make(node.statement === "function" ? POSTGRES_ENTITY_TYPES.function : POSTGRES_ENTITY_TYPES.procedure, q, props);
|
|
1189
|
+
}
|
|
1190
|
+
case "trigger": {
|
|
1191
|
+
const tq = qualified(ctx, node.table);
|
|
1192
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1193
|
+
feed(ctx, node.name.span, "name");
|
|
1194
|
+
// A trigger is its table's: it is named in the table's schema.
|
|
1195
|
+
const q = { schema: tq.schema, name };
|
|
1196
|
+
const comment = applyComments(tag, ctx, comments, q, ["TRIGGER"]);
|
|
1197
|
+
const referencing = {};
|
|
1198
|
+
for (const r of node.referencing)
|
|
1199
|
+
referencing[r.which === "OLD" ? "old" : "new"] = r.name;
|
|
1200
|
+
const props = strip({
|
|
1201
|
+
...q,
|
|
1202
|
+
orReplace: node.orReplace || undefined,
|
|
1203
|
+
constraint: node.constraint || undefined,
|
|
1204
|
+
timing: node.timing.toLowerCase(),
|
|
1205
|
+
events: node.events.map((e) => {
|
|
1206
|
+
for (const c of e.columns)
|
|
1207
|
+
feed(ctx, c.span, "events");
|
|
1208
|
+
const columns = e.columns.map((c) => columnName(ctx, c));
|
|
1209
|
+
return { event: e.event.toLowerCase(), ...(columns.length > 0 ? { columns } : {}) };
|
|
1210
|
+
}),
|
|
1211
|
+
table: target(ctx, node.table),
|
|
1212
|
+
tableName: sqlNameOf(tq),
|
|
1213
|
+
from: node.from ? targetName(ctx, node.from) : undefined,
|
|
1214
|
+
deferrable: node.deferrable,
|
|
1215
|
+
initiallyDeferred: node.initiallyDeferred,
|
|
1216
|
+
referencing: Object.keys(referencing).length > 0 ? referencing : undefined,
|
|
1217
|
+
forEach: (node.forEach ?? "STATEMENT").toLowerCase(),
|
|
1218
|
+
when: text(ctx, node.when, "when"),
|
|
1219
|
+
function: target(ctx, node.function),
|
|
1220
|
+
functionName: targetName(ctx, node.function),
|
|
1221
|
+
args: node.args.map((a) => req(text(ctx, a, "args"))),
|
|
1222
|
+
comment,
|
|
1223
|
+
ddl,
|
|
1224
|
+
source,
|
|
1225
|
+
}, ["events", "args"]);
|
|
1226
|
+
return make(POSTGRES_ENTITY_TYPES.trigger, q, props);
|
|
1227
|
+
}
|
|
1228
|
+
case "policy": {
|
|
1229
|
+
const tq = qualified(ctx, node.table);
|
|
1230
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1231
|
+
feed(ctx, node.name.span, "name");
|
|
1232
|
+
// A policy is its table's, named in the table's schema.
|
|
1233
|
+
const q = { schema: tq.schema, name };
|
|
1234
|
+
const comment = applyComments(tag, ctx, comments, q, ["POLICY"]);
|
|
1235
|
+
const props = stripUnset({
|
|
1236
|
+
...q,
|
|
1237
|
+
table: target(ctx, node.table),
|
|
1238
|
+
tableName: sqlNameOf(tq),
|
|
1239
|
+
permissive: node.permissive !== false,
|
|
1240
|
+
command: (node.command ?? "ALL").toLowerCase(),
|
|
1241
|
+
roles: node.roles.length > 0 ? node.roles.map((r) => roleName(ctx, r)) : ["public"],
|
|
1242
|
+
using: text(ctx, node.using, "using"),
|
|
1243
|
+
check: text(ctx, node.check, "check"),
|
|
1244
|
+
comment,
|
|
1245
|
+
ddl,
|
|
1246
|
+
source,
|
|
1247
|
+
});
|
|
1248
|
+
// AS RESTRICTIVE is a false that means something.
|
|
1249
|
+
props.permissive = node.permissive !== false;
|
|
1250
|
+
return make(POSTGRES_ENTITY_TYPES.policy, q, props);
|
|
1251
|
+
}
|
|
1252
|
+
case "role": {
|
|
1253
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1254
|
+
feed(ctx, node.name.span, "name");
|
|
1255
|
+
const comment = applyComments(tag, ctx, comments, { name }, ["ROLE"]);
|
|
1256
|
+
const value = (o) => {
|
|
1257
|
+
const x = node.options.find((y) => y.option === o);
|
|
1258
|
+
return x?.value ? text(ctx, x.value, "options") : undefined;
|
|
1259
|
+
};
|
|
1260
|
+
const props = stripUnset({
|
|
1261
|
+
name,
|
|
1262
|
+
options: node.options.filter((o) => o.value === undefined).map((o) => o.option.toLowerCase()),
|
|
1263
|
+
connectionLimit: value("CONNECTION LIMIT"),
|
|
1264
|
+
comment,
|
|
1265
|
+
ddl,
|
|
1266
|
+
source,
|
|
1267
|
+
});
|
|
1268
|
+
return make(POSTGRES_ENTITY_TYPES.role, { name }, props);
|
|
1269
|
+
}
|
|
1270
|
+
case "grant":
|
|
1271
|
+
return make(node.defaults ? POSTGRES_ENTITY_TYPES.defaultPrivileges : POSTGRES_ENTITY_TYPES.grant, { name: "" }, grantProps(ctx, node, ddl, source));
|
|
1272
|
+
case "rowSecurity":
|
|
1273
|
+
throw new SqlTemplateError(tag, "an ALTER TABLE ... ROW LEVEL SECURITY goes after the table's CREATE TABLE, in the table's template", 0, 0);
|
|
1274
|
+
case "comment":
|
|
1275
|
+
throw new SqlTemplateError(tag, "a COMMENT ON goes after the CREATE it comments on", 0, 0);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* `` schema`CREATE SCHEMA ...` ``: one Postgres schema, parsed at build time.
|
|
1280
|
+
* Given its name as the type argument (`` schema<"shop">`...` ``), a composite
|
|
1281
|
+
* placed in it types its members' columns (chant#3787).
|
|
1282
|
+
*/
|
|
1283
|
+
export function schema(strings, ...values) {
|
|
1284
|
+
return build("schema", strings, values);
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* `` table`CREATE TABLE ...` ``: one Postgres table with its columns and
|
|
1288
|
+
* constraints, parsed at build time. Given its qualified name as the type
|
|
1289
|
+
* argument (`` table<"shop.orders">`...` ``), its columns are the ones
|
|
1290
|
+
* `chant sql types` declared for that name.
|
|
1291
|
+
*/
|
|
1292
|
+
export function table(strings, ...values) {
|
|
1293
|
+
return build("table", strings, values);
|
|
1294
|
+
}
|
|
1295
|
+
/** `` index`CREATE [UNIQUE] INDEX [CONCURRENTLY] name ON ...` ``: one named index. */
|
|
1296
|
+
export function index(strings, ...values) {
|
|
1297
|
+
return build("index", strings, values);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* `` view`CREATE [MATERIALIZED] VIEW ...` ``: one view or materialized view,
|
|
1301
|
+
* with what it reads and its column lineage. Takes its qualified name as the
|
|
1302
|
+
* type argument, as {@link table} does.
|
|
1303
|
+
*/
|
|
1304
|
+
export function view(strings, ...values) {
|
|
1305
|
+
return build("view", strings, values);
|
|
1306
|
+
}
|
|
1307
|
+
/** `` sequence`CREATE SEQUENCE ...` ``: one sequence. Reference it as `nextval(${seq})`. */
|
|
1308
|
+
export function sequence(strings, ...values) {
|
|
1309
|
+
return build("sequence", strings, values);
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* `` type`CREATE TYPE name AS ENUM (...)` ``: one enum type; `AS (attribute
|
|
1313
|
+
* type, ...)` a composite type, `AS RANGE (SUBTYPE = ...)` a range type (and
|
|
1314
|
+
* its multirange type, which Postgres creates with it).
|
|
1315
|
+
*/
|
|
1316
|
+
export function type(strings, ...values) {
|
|
1317
|
+
return build("type", strings, values);
|
|
1318
|
+
}
|
|
1319
|
+
/** `` domain`CREATE DOMAIN ...` ``: one domain. */
|
|
1320
|
+
export function domain(strings, ...values) {
|
|
1321
|
+
return build("domain", strings, values);
|
|
1322
|
+
}
|
|
1323
|
+
/** `` extension`CREATE EXTENSION ...` ``: one extension. */
|
|
1324
|
+
export function extension(strings, ...values) {
|
|
1325
|
+
return build("extension", strings, values);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* `` func`CREATE [OR REPLACE] FUNCTION name(args) RETURNS ... LANGUAGE ... AS $$ ... $$` ``:
|
|
1329
|
+
* one function, its body a string kept verbatim. (`function` is a JavaScript
|
|
1330
|
+
* key word, hence `func`.)
|
|
1331
|
+
*/
|
|
1332
|
+
export function func(strings, ...values) {
|
|
1333
|
+
return build("func", strings, values);
|
|
1334
|
+
}
|
|
1335
|
+
/** `` procedure`CREATE [OR REPLACE] PROCEDURE name(args) LANGUAGE ... AS $$ ... $$` ``: one procedure. */
|
|
1336
|
+
export function procedure(strings, ...values) {
|
|
1337
|
+
return build("procedure", strings, values);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* `` aggregate`CREATE [OR REPLACE] AGGREGATE name(argtype, ...) (SFUNC = ..., STYPE = ...)` ``:
|
|
1341
|
+
* one aggregate, identified, as a function is, by its argument types.
|
|
1342
|
+
*/
|
|
1343
|
+
export function aggregate(strings, ...values) {
|
|
1344
|
+
return build("aggregate", strings, values);
|
|
1345
|
+
}
|
|
1346
|
+
/** `` collation`CREATE COLLATION name (provider = icu, locale = '...')` ``: one collation. */
|
|
1347
|
+
export function collation(strings, ...values) {
|
|
1348
|
+
return build("collation", strings, values);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* `` cast`CREATE CAST (source AS target) WITH FUNCTION ${fn}(source) [AS ASSIGNMENT | AS IMPLICIT]` ``:
|
|
1352
|
+
* one cast, named by its two types. Creating one takes owning the source or
|
|
1353
|
+
* the target type.
|
|
1354
|
+
*/
|
|
1355
|
+
export function cast(strings, ...values) {
|
|
1356
|
+
return build("cast", strings, values);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* `` eventTrigger`CREATE EVENT TRIGGER name ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION ${fn}()` ``:
|
|
1360
|
+
* one event trigger, the database's (no schema). Creating one takes a superuser.
|
|
1361
|
+
*/
|
|
1362
|
+
export function eventTrigger(strings, ...values) {
|
|
1363
|
+
return build("eventTrigger", strings, values);
|
|
1364
|
+
}
|
|
1365
|
+
/** `` trigger`CREATE [OR REPLACE] [CONSTRAINT] TRIGGER name ... ON ${table} ... EXECUTE FUNCTION ${fn}()` ``: one trigger on its table. */
|
|
1366
|
+
export function trigger(strings, ...values) {
|
|
1367
|
+
return build("trigger", strings, values);
|
|
1368
|
+
}
|
|
1369
|
+
/** `` policy`CREATE POLICY name ON ${table} ... USING (...)` ``: one row-level security policy on its table. */
|
|
1370
|
+
export function policy(strings, ...values) {
|
|
1371
|
+
return build("policy", strings, values);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* `` role`CREATE ROLE name [WITH] options` ``: a role the schema needs, created
|
|
1375
|
+
* when it is missing. A password and memberships are the environment's and are
|
|
1376
|
+
* refused; a role the environment provisions is named as text where it is
|
|
1377
|
+
* granted to, never declared.
|
|
1378
|
+
*/
|
|
1379
|
+
export function role(strings, ...values) {
|
|
1380
|
+
return build("role", strings, values);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* `` grant`GRANT ... ON ... TO ...` ``, `REVOKE ... FROM ...`, or
|
|
1384
|
+
* `ALTER DEFAULT PRIVILEGES ... GRANT|REVOKE ...`: privileges on schemas,
|
|
1385
|
+
* tables, columns, sequences and routines, applied where the profile manages
|
|
1386
|
+
* access (`sql.profiles.<env>.access`).
|
|
1387
|
+
*/
|
|
1388
|
+
export function grant(strings, ...values) {
|
|
1389
|
+
return build("grant", strings, values);
|
|
1390
|
+
}
|