@intentius/chant-lexicon-sql 0.124.19 → 0.124.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/clickhouse/access.d.ts +36 -7
  2. package/dist/clickhouse/access.d.ts.map +1 -1
  3. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  4. package/dist/clickhouse/entities.d.ts +60 -0
  5. package/dist/clickhouse/entities.d.ts.map +1 -1
  6. package/dist/clickhouse/import/generator.d.ts +1 -7
  7. package/dist/clickhouse/import/generator.d.ts.map +1 -1
  8. package/dist/clickhouse/live/catalog.d.ts +6 -5
  9. package/dist/clickhouse/live/catalog.d.ts.map +1 -1
  10. package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
  11. package/dist/clickhouse/parser.d.ts +16 -0
  12. package/dist/clickhouse/parser.d.ts.map +1 -1
  13. package/dist/clickhouse/plan/deep.d.ts.map +1 -1
  14. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  15. package/dist/clickhouse/plan/normalize.d.ts +6 -3
  16. package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
  17. package/dist/clickhouse/plan/rules.d.ts +3 -0
  18. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  19. package/dist/clickhouse/topology.d.ts.map +1 -1
  20. package/dist/clickhouse.d.ts +1 -1
  21. package/dist/clickhouse.d.ts.map +1 -1
  22. package/dist/codegen/registry.d.ts.map +1 -1
  23. package/dist/coverage/clickhouse.json +8 -8
  24. package/dist/coverage/postgres.json +6 -6
  25. package/dist/files/clickhouse.d.ts +2 -1
  26. package/dist/files/clickhouse.d.ts.map +1 -1
  27. package/dist/files/postgres.d.ts +1 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/integrity.json +10 -10
  31. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  32. package/dist/lint/post-synth/sql101.d.ts.map +1 -1
  33. package/dist/lint/rules/sqlch001.d.ts +1 -1
  34. package/dist/lint/rules/sqlch001.d.ts.map +1 -1
  35. package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
  36. package/dist/lint/rules/templates.d.ts +1 -1
  37. package/dist/lint/rules/templates.d.ts.map +1 -1
  38. package/dist/lsp/postgres-template.d.ts.map +1 -1
  39. package/dist/manifest.json +1 -1
  40. package/dist/meta.json +40 -0
  41. package/dist/okf/index.md +9 -1
  42. package/dist/okf/rules/SQL101.md +1 -0
  43. package/dist/okf/rules/SQLCH001.md +2 -2
  44. package/dist/okf/rules/SQLPG113.md +2 -0
  45. package/dist/okf/types/PostgresAggregate.md +13 -0
  46. package/dist/okf/types/PostgresCast.md +9 -0
  47. package/dist/okf/types/PostgresCollation.md +9 -0
  48. package/dist/okf/types/PostgresCompositeType.md +13 -0
  49. package/dist/okf/types/PostgresEventTrigger.md +9 -0
  50. package/dist/okf/types/PostgresRangeType.md +13 -0
  51. package/dist/okf/types/Quota.md +9 -0
  52. package/dist/okf/types/SettingsProfile.md +9 -0
  53. package/dist/plugin.d.ts.map +1 -1
  54. package/dist/postgres/apply/apply.d.ts.map +1 -1
  55. package/dist/postgres/apply/statements.d.ts +4 -3
  56. package/dist/postgres/apply/statements.d.ts.map +1 -1
  57. package/dist/postgres/entities.d.ts +129 -6
  58. package/dist/postgres/entities.d.ts.map +1 -1
  59. package/dist/postgres/entity-types.d.ts +6 -0
  60. package/dist/postgres/entity-types.d.ts.map +1 -1
  61. package/dist/postgres/import/generator.d.ts +3 -2
  62. package/dist/postgres/import/generator.d.ts.map +1 -1
  63. package/dist/postgres/live/catalog-kinds.d.ts +28 -0
  64. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -0
  65. package/dist/postgres/live/catalog.d.ts +15 -11
  66. package/dist/postgres/live/catalog.d.ts.map +1 -1
  67. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  68. package/dist/postgres/parser.d.ts +88 -5
  69. package/dist/postgres/parser.d.ts.map +1 -1
  70. package/dist/postgres/plan/deep.d.ts.map +1 -1
  71. package/dist/postgres/plan/diff.d.ts.map +1 -1
  72. package/dist/postgres/plan/normalize.d.ts +17 -2
  73. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  74. package/dist/postgres/plan/rules.d.ts +3 -0
  75. package/dist/postgres/plan/rules.d.ts.map +1 -1
  76. package/dist/postgres/plan/schema.d.ts +3 -2
  77. package/dist/postgres/plan/schema.d.ts.map +1 -1
  78. package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
  79. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  80. package/dist/postgres.d.ts +1 -1
  81. package/dist/postgres.d.ts.map +1 -1
  82. package/dist/rules/postgres-names.ts +15 -2
  83. package/dist/rules/postgres-templates.ts +2 -2
  84. package/dist/rules/sql101.ts +1 -0
  85. package/dist/rules/sqlch001.ts +10 -3
  86. package/dist/rules/sqlpg001.ts +6 -21
  87. package/dist/rules/sqlpg113.ts +1 -1
  88. package/dist/rules/templates.ts +2 -2
  89. package/dist/validate.d.ts.map +1 -1
  90. package/package.json +2 -2
  91. package/src/clickhouse/access.ts +197 -22
  92. package/src/clickhouse/apply/statements.ts +17 -5
  93. package/src/clickhouse/entities.ts +78 -2
  94. package/src/clickhouse/import/generator.ts +16 -3
  95. package/src/clickhouse/live/catalog.ts +57 -10
  96. package/src/clickhouse/live/describe-resources.ts +3 -1
  97. package/src/clickhouse/live/kinds.e2e.test.ts +179 -0
  98. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -4
  99. package/src/clickhouse/parser.test.ts +45 -2
  100. package/src/clickhouse/parser.ts +111 -3
  101. package/src/clickhouse/plan/deep.ts +25 -6
  102. package/src/clickhouse/plan/diff.ts +41 -1
  103. package/src/clickhouse/plan/normalize.ts +12 -4
  104. package/src/clickhouse/plan/rules.ts +22 -1
  105. package/src/clickhouse/quota-profile.test.ts +186 -0
  106. package/src/clickhouse/table-function.test.ts +54 -0
  107. package/src/clickhouse/testdata/server-ddl-baseline.json +17 -14
  108. package/src/clickhouse/topology.ts +4 -2
  109. package/src/clickhouse.ts +6 -0
  110. package/src/codegen/postgres-classifier-page.ts +1 -1
  111. package/src/codegen/registry.ts +8 -0
  112. package/src/core/undeclarable.test.ts +22 -19
  113. package/src/coverage/clickhouse.json +8 -8
  114. package/src/coverage/postgres.json +6 -6
  115. package/src/files/clickhouse.ts +14 -6
  116. package/src/files/postgres.ts +19 -5
  117. package/src/generated/lexicon-sql.json +40 -0
  118. package/src/index.ts +2 -2
  119. package/src/lint/post-synth/postgres-names.ts +15 -2
  120. package/src/lint/post-synth/sql101.ts +1 -0
  121. package/src/lint/post-synth/sqlpg113.ts +1 -1
  122. package/src/lint/rules/postgres-templates.ts +2 -2
  123. package/src/lint/rules/sqlch001.ts +10 -3
  124. package/src/lint/rules/sqlpg001.ts +6 -21
  125. package/src/lint/rules/templates.ts +2 -2
  126. package/src/lsp/postgres-template.ts +14 -0
  127. package/src/mcp/index.ts +5 -5
  128. package/src/mcp/postgres.ts +1 -1
  129. package/src/plugin.ts +7 -1
  130. package/src/postgres/apply/apply.ts +8 -2
  131. package/src/postgres/apply/statements.ts +29 -6
  132. package/src/postgres/entities.test.ts +2 -2
  133. package/src/postgres/entities.ts +316 -28
  134. package/src/postgres/entity-types.ts +6 -0
  135. package/src/postgres/import/generator.ts +31 -4
  136. package/src/postgres/kinds.test.ts +140 -0
  137. package/src/postgres/live/catalog-kinds.ts +312 -0
  138. package/src/postgres/live/catalog.ts +30 -12
  139. package/src/postgres/live/describe-resources.ts +14 -5
  140. package/src/postgres/live/kinds.e2e.test.ts +182 -0
  141. package/src/postgres/live/roundtrip.e2e.test.ts +9 -3
  142. package/src/postgres/live/undeclarable.e2e.test.ts +15 -13
  143. package/src/postgres/parser.test.ts +2 -1
  144. package/src/postgres/parser.ts +307 -17
  145. package/src/postgres/plan/deep.ts +33 -0
  146. package/src/postgres/plan/diff.ts +45 -0
  147. package/src/postgres/plan/normalize.ts +153 -2
  148. package/src/postgres/plan/rules.ts +21 -0
  149. package/src/postgres/plan/schema.ts +6 -4
  150. package/src/postgres/testing/roundtrip-schema.ts +19 -1
  151. package/src/postgres.ts +19 -0
  152. package/src/skill-defs.ts +1 -1
  153. package/src/skills/chant-sql-postgres.md +7 -3
  154. package/src/skills/chant-sql.md +3 -1
  155. package/src/unknown-clauses.test.ts +2 -1
  156. package/src/validate.ts +8 -0
@@ -81,7 +81,7 @@ export function postgresSearch(params: Record<string, unknown>): unknown {
81
81
  };
82
82
  }
83
83
 
84
- const TAGS = { schema: pg.schema, table: pg.table, index: pg.index, view: pg.view, sequence: pg.sequence, type: pg.type, domain: pg.domain, extension: pg.extension, func: pg.func, procedure: pg.procedure, trigger: pg.trigger, policy: pg.policy, role: pg.role, grant: pg.grant } as const;
84
+ const TAGS = { schema: pg.schema, table: pg.table, index: pg.index, view: pg.view, sequence: pg.sequence, type: pg.type, domain: pg.domain, extension: pg.extension, func: pg.func, procedure: pg.procedure, aggregate: pg.aggregate, trigger: pg.trigger, eventTrigger: pg.eventTrigger, collation: pg.collation, cast: pg.cast, policy: pg.policy, role: pg.role, grant: pg.grant } as const;
85
85
  export const POSTGRES_TAGS = Object.keys(TAGS);
86
86
 
87
87
  /** Parse one statement with the Postgres tag; an error comes back as SQLPG001 with its line and column. */
package/src/plugin.ts CHANGED
@@ -99,15 +99,21 @@ export const sqlPlugin: LexiconPlugin = {
99
99
  },
100
100
  { name: "dictionary", isTag: true, description: "A ClickHouse CREATE DICTIONARY, parsed into a dictionary entity with its attributes, key, source and layout" },
101
101
  { name: "user", isTag: true, description: "A ClickHouse CREATE USER, parsed into a user entity; its password is the environment's" },
102
+ { name: "quota", isTag: true, description: "A ClickHouse CREATE QUOTA, parsed into a quota entity with its key, its intervals' limits and whom it applies to" },
103
+ { name: "settingsProfile", isTag: true, description: "A ClickHouse CREATE SETTINGS PROFILE, parsed into a settings profile entity with its settings, constraints and inherited profiles" },
102
104
  { name: "schema", isTag: true, description: "A Postgres CREATE SCHEMA, parsed into a schema entity" },
103
105
  { name: "index", isTag: true, description: "A Postgres CREATE INDEX, parsed into an index entity" },
104
106
  { name: "sequence", isTag: true, description: "A Postgres CREATE SEQUENCE, parsed into a sequence entity" },
105
- { name: "type", isTag: true, description: "A Postgres CREATE TYPE ... AS ENUM, parsed into an enum entity" },
107
+ { name: "type", isTag: true, description: "A Postgres CREATE TYPE ... AS ENUM, AS (...) or AS RANGE, parsed into an enum, composite or range type entity" },
106
108
  { name: "domain", isTag: true, description: "A Postgres CREATE DOMAIN, parsed into a domain entity" },
107
109
  { name: "extension", isTag: true, description: "A Postgres CREATE EXTENSION, parsed into an extension entity" },
108
110
  { name: "func", isTag: true, description: "A Postgres CREATE FUNCTION, parsed into a function entity with its body kept verbatim, or a ClickHouse CREATE FUNCTION (a named lambda)" },
109
111
  { name: "procedure", isTag: true, description: "A Postgres CREATE PROCEDURE, parsed into a procedure entity with its body kept verbatim" },
112
+ { name: "aggregate", isTag: true, description: "A Postgres CREATE AGGREGATE, parsed into an aggregate entity identified by its argument types" },
110
113
  { name: "trigger", isTag: true, description: "A Postgres CREATE TRIGGER, parsed into a trigger entity on its table" },
114
+ { name: "eventTrigger", isTag: true, description: "A Postgres CREATE EVENT TRIGGER, parsed into the database's event trigger entity" },
115
+ { name: "collation", isTag: true, description: "A Postgres CREATE COLLATION, parsed into a collation entity" },
116
+ { name: "cast", isTag: true, description: "A Postgres CREATE CAST, parsed into a cast entity named by its two types" },
111
117
  { name: "policy", isTag: true, description: "A Postgres CREATE POLICY, parsed into a row-level security policy on its table, or a ClickHouse CREATE ROW POLICY" },
112
118
  { name: "role", isTag: true, description: "A Postgres CREATE ROLE without a password or memberships, parsed into a role entity, or a ClickHouse CREATE ROLE" },
113
119
  { name: "grant", isTag: true, description: "A Postgres GRANT, REVOKE or ALTER DEFAULT PRIVILEGES, parsed into the privileges it declares, or a ClickHouse GRANT of privileges or roles" },
@@ -151,7 +151,7 @@ export const refName = (o: { kind: CanonicalKind; schema?: string; name: string;
151
151
  ? `${o.name}${o.signature ?? ""}`
152
152
  : o.kind === "schema" || o.kind === "extension" || o.kind === "role" || o.schema === undefined
153
153
  ? o.name
154
- : `${o.schema}.${o.name}${o.kind === "function" || o.kind === "procedure" ? (o.signature ?? "") : ""}`;
154
+ : `${o.schema}.${o.name}${o.kind === "function" || o.kind === "procedure" || o.kind === "aggregate" ? (o.signature ?? "") : ""}`;
155
155
 
156
156
  /**
157
157
  * The objects a Postgres `chant build` output declares, in creation order,
@@ -564,7 +564,13 @@ function liveCanonicalAddress(o: LivePgObject): { kind: CanonicalKind; schema?:
564
564
  "Postgres::MaterializedView": "materializedView",
565
565
  "Postgres::Sequence": "sequence",
566
566
  "Postgres::Enum": "enum",
567
+ "Postgres::CompositeType": "composite",
568
+ "Postgres::RangeType": "range",
567
569
  "Postgres::Domain": "domain",
570
+ "Postgres::Aggregate": "aggregate",
571
+ "Postgres::Collation": "collation",
572
+ "Postgres::Cast": "cast",
573
+ "Postgres::EventTrigger": "eventTrigger",
568
574
  "Postgres::Extension": "extension",
569
575
  "Postgres::Function": "function",
570
576
  "Postgres::Procedure": "procedure",
@@ -577,7 +583,7 @@ function liveCanonicalAddress(o: LivePgObject): { kind: CanonicalKind; schema?:
577
583
  )[o.type];
578
584
  return {
579
585
  kind,
580
- ...(o.schema !== undefined && kind !== "schema" && kind !== "extension" && kind !== "role" ? { schema: o.schema } : {}),
586
+ ...(o.schema !== undefined && kind !== "schema" && kind !== "extension" && kind !== "role" && kind !== "cast" && kind !== "eventTrigger" ? { schema: o.schema } : {}),
581
587
  name: o.name,
582
588
  ...(o.signature !== undefined ? { signature: o.signature } : {}),
583
589
  };
@@ -100,12 +100,15 @@ export const pgString = (value: string): string => `'${value.replace(/'/g, "''")
100
100
  export const pgQualified = (schema: string | undefined, name: string): string => (schema ? `${quoteIdent(schema)}.${quoteIdent(name)}` : quoteIdent(name));
101
101
 
102
102
  /**
103
- * An object's name in a statement. A schema and an extension have no schema;
104
- * a routine is named with its parameter types (`app.f(integer,text)`), a
105
- * trigger with its table (`touch ON app.users`).
103
+ * An object's name in a statement. A schema, an extension and an event
104
+ * trigger have no schema; a routine or an aggregate is named with its
105
+ * parameter types (`app.f(integer,text)`), a trigger with its table
106
+ * (`touch ON app.users`), a cast by its types (`(app.money AS numeric)`).
106
107
  */
107
108
  export const objectIdent = (o: { kind: CanonicalKind; schema?: string; name: string; signature?: string }): string =>
108
- o.kind === "schema" || o.kind === "extension" || o.kind === "role"
109
+ o.kind === "cast"
110
+ ? o.name
111
+ : o.kind === "schema" || o.kind === "extension" || o.kind === "role" || o.kind === "eventTrigger"
109
112
  ? quoteIdent(o.name)
110
113
  : o.kind === "trigger" || o.kind === "policy"
111
114
  ? `${quoteIdent(o.name)}${o.signature ?? ""}`
@@ -120,10 +123,16 @@ export const KIND_WORDS: Readonly<Record<CanonicalKind, string>> = {
120
123
  materializedView: "MATERIALIZED VIEW",
121
124
  sequence: "SEQUENCE",
122
125
  enum: "TYPE",
126
+ composite: "TYPE",
127
+ range: "TYPE",
123
128
  domain: "DOMAIN",
124
129
  extension: "EXTENSION",
125
130
  function: "FUNCTION",
126
131
  procedure: "PROCEDURE",
132
+ aggregate: "AGGREGATE",
133
+ collation: "COLLATION",
134
+ cast: "CAST",
135
+ eventTrigger: "EVENT TRIGGER",
127
136
  trigger: "TRIGGER",
128
137
  policy: "POLICY",
129
138
  role: "ROLE",
@@ -254,7 +263,12 @@ export function createSteps(
254
263
  // A table's row-level security is access, which only a profile that manages access applies.
255
264
  if (i > 0 && opts.access === false && firstNode(s)?.statement === "rowSecurity") return;
256
265
  let sql = s.text;
257
- const replaceable = obj.canonical.kind === "view" || obj.canonical.kind === "function" || obj.canonical.kind === "procedure" || (obj.canonical.kind === "trigger" && obj.canonical.fields.constraint !== true);
266
+ const replaceable =
267
+ obj.canonical.kind === "view" ||
268
+ obj.canonical.kind === "function" ||
269
+ obj.canonical.kind === "procedure" ||
270
+ obj.canonical.kind === "aggregate" ||
271
+ (obj.canonical.kind === "trigger" && obj.canonical.fields.constraint !== true);
258
272
  if (i === 0 && opts.orReplace && replaceable && !/^\s*CREATE\s+OR\s+REPLACE\b/i.test(sql)) sql = sql.replace(/\bCREATE\s+/i, "CREATE OR REPLACE ");
259
273
  const concurrently = i === 0 && obj.canonical.kind === "index" && obj.props.concurrently === true;
260
274
  out.push({ ...step(sql, cls, opts.rule, !concurrently), ...(concurrently ? { buildsIndex: objectIdent(obj.canonical) } : {}) });
@@ -528,7 +542,7 @@ export const isRefused = (c: PgChange): boolean => c.class === "expand";
528
542
  export const isDestructive = (c: PgChange): boolean => c.rule === "SQLPG204";
529
543
 
530
544
  /** Changes that replace the object: drop it and create it again from its declaration. */
531
- export const isRecreate = (c: PgChange): boolean => c.rule === "SQLPG252" || c.rule === "SQLPG243" || c.rule === "SQLPG281";
545
+ export const isRecreate = (c: PgChange): boolean => c.rule === "SQLPG252" || c.rule === "SQLPG243" || c.rule === "SQLPG281" || c.rule === "SQLPG300";
532
546
 
533
547
  /**
534
548
  * SET NOT NULL on a table that exists, without reading the table under
@@ -692,6 +706,7 @@ export function alterSteps(
692
706
  break;
693
707
  case "SQLPG250":
694
708
  case "SQLPG280":
709
+ case "SQLPG302":
695
710
  steps.push(...createSteps(obj, opts.marker, { orReplace: true, cls: c.class, rule: c.rule }).slice(0, 1));
696
711
  break;
697
712
  case "SQLPG291": {
@@ -872,11 +887,19 @@ const DROP_ORDER: Record<CanonicalKind, number> = {
872
887
  index: 3,
873
888
  table: 4,
874
889
  sequence: 5,
890
+ // An event trigger and a cast before the function they call; an aggregate before its functions.
891
+ eventTrigger: 5.5,
892
+ cast: 5.5,
893
+ aggregate: 5.5,
875
894
  // A routine after what calls it (a view, a trigger, a default), before the types its parameters use.
876
895
  function: 6,
877
896
  procedure: 6,
878
897
  domain: 7,
898
+ composite: 7.5,
899
+ range: 7.5,
879
900
  enum: 8,
901
+ // A collation after the types and columns that use it.
902
+ collation: 8.5,
880
903
  extension: 9,
881
904
  schema: 10,
882
905
  // A role is the cluster's: never dropped by a prune (`../live/catalog.ts` reads only the declared ones).
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
  import { isDeclarable } from "@intentius/chant/declarable";
3
3
  import { getInterpolationFields } from "@intentius/chant/provenance";
4
- import { domain, extension, index, isColumnRef, literal, schema, sequence, SqlTemplateError, table, type, view } from "./entities";
4
+ import { domain, extension, index, isColumnRef, literal, schema, sequence, SqlTemplateError, table, type, view, type EnumProps } from "./entities";
5
5
 
6
6
  const app = schema`CREATE SCHEMA app`;
7
7
  const orderStatus = type`CREATE TYPE ${app}.order_status AS ENUM ('placed', 'paid')`;
@@ -121,7 +121,7 @@ describe("indexes, views and the rest", () => {
121
121
  const e = extension`CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA ${app} VERSION '1.6'`;
122
122
  expect(e.props).toMatchObject({ name: "citext", schema: "app", version: "1.6", ifNotExists: true });
123
123
  expect(e.dependsOn).toEqual([app]);
124
- expect(orderStatus.props.labels).toEqual(["placed", "paid"]);
124
+ expect((orderStatus.props as EnumProps).labels).toEqual(["placed", "paid"]);
125
125
  });
126
126
  });
127
127
 
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * The Postgres entities: `schema`, `table`, `index`, `view`, `sequence`,
3
- * `type`, `domain`, `extension`, `func`, `procedure`, `trigger`, `policy`,
4
- * `role` and `grant` tagged templates that parse their DDL at fold time into
5
- * one Declarable each (chant #3278, #3279, #3680, #3681).
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).
6
7
  *
7
8
  * ```ts
8
9
  * import { schema, table, index } from "@intentius/chant-lexicon-sql/postgres";
@@ -56,7 +57,7 @@
56
57
  import type { AttrRef } from "@intentius/chant/attrref";
57
58
  import { setInterpolationFields } from "@intentius/chant/provenance";
58
59
  import { isTrivia, POSTGRES_LEXICAL, SqlSyntaxError, untokenize, type Token } from "./tokens";
59
- import { identValue, parseStatements, type ColumnNode, type CommentNode, type ConstraintNode, type GrantNode, type NameNode, type RowSecurityNode, type Span, type StatementNode } from "./parser";
60
+ import { identValue, parseStatements, type ColumnNode, type CommentNode, type ConstraintNode, type DefElem, type GrantNode, type NameNode, type RowSecurityNode, type Span, type StatementNode } from "./parser";
60
61
  import { keywordCategory, quoteIdent } from "./keywords";
61
62
  import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, unparsedClauses, type TemplateCtx, type TemplateDialect, type UnparsedClause } from "../core/interpolation";
62
63
  import { SqlObject, isColumnRefOf, isSqlObjectOf, makeSqlEntity } from "../core/entity";
@@ -260,6 +261,86 @@ export interface EnumProps extends CommonProps {
260
261
  labels: string[];
261
262
  }
262
263
 
264
+ /** A composite type's attribute, as written. */
265
+ export interface CompositeAttributeDef {
266
+ name: string;
267
+ type: string;
268
+ collate?: string;
269
+ }
270
+
271
+ export interface CompositeTypeProps extends CommonProps {
272
+ attributes: CompositeAttributeDef[];
273
+ }
274
+
275
+ export interface RangeTypeProps extends CommonProps {
276
+ /** `SUBTYPE`: the element type. */
277
+ subtype: string;
278
+ subtypeOpclass?: string;
279
+ collation?: string;
280
+ /** `CANONICAL`: the function that canonicalizes a value (a C function). */
281
+ canonical?: string;
282
+ subtypeDiff?: string;
283
+ /** `MULTIRANGE_TYPE_NAME`: the multirange type's name, when it is not the one Postgres derives. */
284
+ multirangeTypeName?: string;
285
+ }
286
+
287
+ /** One argument of an aggregate, as written. */
288
+ export interface AggregateArgDef {
289
+ /** `in` when not written. */
290
+ mode: "in" | "variadic";
291
+ name?: string;
292
+ type: string;
293
+ }
294
+
295
+ export interface AggregateProps extends CommonProps {
296
+ orReplace?: boolean;
297
+ /** The arguments; empty for `(*)`. An ordered-set aggregate's direct arguments. */
298
+ args: AggregateArgDef[];
299
+ /** An ordered-set aggregate's aggregated arguments (`ORDER BY ...`). */
300
+ orderBy?: AggregateArgDef[];
301
+ /**
302
+ * The definition: each option by its name, folded (`sfunc`, `stype`,
303
+ * `initcond`), to its value as written; an option given without a value
304
+ * (`HYPOTHETICAL`, `FINALFUNC_EXTRA`) to `true`.
305
+ */
306
+ options: Record<string, string>;
307
+ }
308
+
309
+ export interface CollationProps extends CommonProps {
310
+ ifNotExists?: boolean;
311
+ /** Each option by its name, folded (`provider`, `locale`, `deterministic`), to its value as written. */
312
+ options: Record<string, string>;
313
+ }
314
+
315
+ export interface CastProps extends Omit<CommonProps, "schema"> {
316
+ /** `(source AS target)`: a cast is named by its two types and lives in no schema. */
317
+ name: string;
318
+ /** The source type as written. */
319
+ sourceType: string;
320
+ /** The target type as written. */
321
+ targetType: string;
322
+ /** `WITH FUNCTION` (`function`), `WITHOUT FUNCTION` (`binary`) or `WITH INOUT` (`inout`). */
323
+ method: "function" | "binary" | "inout";
324
+ /** The cast function: the entity when interpolated, else its name. */
325
+ function?: PostgresObject | string;
326
+ /** The cast function's SQL name. */
327
+ functionName?: string;
328
+ /** The cast function's parameter types, when written. */
329
+ functionArgs?: string;
330
+ /** `explicit` (the default), `AS ASSIGNMENT` or `AS IMPLICIT`. */
331
+ context: "explicit" | "assignment" | "implicit";
332
+ }
333
+
334
+ export interface EventTriggerProps extends Omit<CommonProps, "schema"> {
335
+ /** `ddl_command_start`, `ddl_command_end`, `sql_drop`, `table_rewrite` or `login`. */
336
+ event: string;
337
+ /** `WHEN TAG IN (...)`: the command tags it fires for; every one when absent. */
338
+ tags?: string[];
339
+ /** The function it executes: the entity when interpolated, else its name. */
340
+ function: PostgresObject | string;
341
+ functionName: string;
342
+ }
343
+
263
344
  export interface DomainProps extends CommonProps {
264
345
  /** The underlying data type. */
265
346
  dataType: string;
@@ -488,11 +569,44 @@ export interface PostgresEnum extends PostgresObject {
488
569
  readonly props: EnumProps;
489
570
  }
490
571
 
572
+ export interface PostgresCompositeType extends PostgresObject {
573
+ readonly entityType: "Postgres::CompositeType";
574
+ readonly props: CompositeTypeProps;
575
+ }
576
+
577
+ export interface PostgresRangeType extends PostgresObject {
578
+ readonly entityType: "Postgres::RangeType";
579
+ readonly props: RangeTypeProps;
580
+ }
581
+
582
+ /** What the `type` tag declares: an enum, a composite or a range type. */
583
+ export type PostgresType = PostgresEnum | PostgresCompositeType | PostgresRangeType;
584
+
491
585
  export interface PostgresDomain extends PostgresObject {
492
586
  readonly entityType: "Postgres::Domain";
493
587
  readonly props: DomainProps;
494
588
  }
495
589
 
590
+ export interface PostgresAggregate extends PostgresObject {
591
+ readonly entityType: "Postgres::Aggregate";
592
+ readonly props: AggregateProps;
593
+ }
594
+
595
+ export interface PostgresCollation extends PostgresObject {
596
+ readonly entityType: "Postgres::Collation";
597
+ readonly props: CollationProps;
598
+ }
599
+
600
+ export interface PostgresCast extends PostgresObject {
601
+ readonly entityType: "Postgres::Cast";
602
+ readonly props: CastProps;
603
+ }
604
+
605
+ export interface PostgresEventTrigger extends PostgresObject {
606
+ readonly entityType: "Postgres::EventTrigger";
607
+ readonly props: EventTriggerProps;
608
+ }
609
+
496
610
  export interface PostgresExtension extends PostgresObject {
497
611
  readonly entityType: "Postgres::Extension";
498
612
  readonly props: ExtensionProps;
@@ -637,23 +751,46 @@ export const POSTGRES_TEMPLATES: TemplateDialect = {
637
751
 
638
752
  type Ctx = TemplateCtx;
639
753
 
640
- export type PostgresTag = "schema" | "table" | "index" | "view" | "sequence" | "type" | "domain" | "extension" | "func" | "procedure" | "trigger" | "policy" | "role" | "grant";
641
-
642
- const STATEMENT_OF: Record<PostgresTag, StatementNode["statement"]> = {
643
- schema: "schema",
644
- table: "table",
645
- index: "index",
646
- view: "view",
647
- sequence: "sequence",
648
- type: "enum",
649
- domain: "domain",
650
- extension: "extension",
651
- func: "function",
652
- procedure: "procedure",
653
- trigger: "trigger",
654
- policy: "policy",
655
- role: "role",
656
- grant: "grant",
754
+ export type PostgresTag =
755
+ | "schema"
756
+ | "table"
757
+ | "index"
758
+ | "view"
759
+ | "sequence"
760
+ | "type"
761
+ | "domain"
762
+ | "extension"
763
+ | "func"
764
+ | "procedure"
765
+ | "aggregate"
766
+ | "trigger"
767
+ | "eventTrigger"
768
+ | "collation"
769
+ | "cast"
770
+ | "policy"
771
+ | "role"
772
+ | "grant";
773
+
774
+ /** The statements each tag holds. */
775
+ export const STATEMENTS_OF: Record<PostgresTag, readonly StatementNode["statement"][]> = {
776
+ schema: ["schema"],
777
+ table: ["table"],
778
+ index: ["index"],
779
+ view: ["view"],
780
+ sequence: ["sequence"],
781
+ type: ["enum", "composite", "range"],
782
+ domain: ["domain"],
783
+ extension: ["extension"],
784
+ func: ["function"],
785
+ procedure: ["procedure"],
786
+ aggregate: ["aggregate"],
787
+ trigger: ["trigger"],
788
+ eventTrigger: ["eventTrigger"],
789
+ collation: ["collation"],
790
+ cast: ["cast"],
791
+ policy: ["policy"],
792
+ role: ["role"],
793
+ grant: ["grant"],
657
794
  };
658
795
 
659
796
  const TAG_OF: Record<StatementNode["statement"], PostgresTag | undefined> = {
@@ -663,11 +800,17 @@ const TAG_OF: Record<StatementNode["statement"], PostgresTag | undefined> = {
663
800
  view: "view",
664
801
  sequence: "sequence",
665
802
  enum: "type",
803
+ composite: "type",
804
+ range: "type",
666
805
  domain: "domain",
667
806
  extension: "extension",
668
807
  function: "func",
669
808
  procedure: "procedure",
809
+ aggregate: "aggregate",
670
810
  trigger: "trigger",
811
+ eventTrigger: "eventTrigger",
812
+ collation: "collation",
813
+ cast: "cast",
671
814
  policy: "policy",
672
815
  role: "role",
673
816
  grant: "grant",
@@ -683,11 +826,17 @@ export const STATEMENT_NAMES: Record<StatementNode["statement"], string> = {
683
826
  view: "CREATE VIEW",
684
827
  sequence: "CREATE SEQUENCE",
685
828
  enum: "CREATE TYPE",
829
+ composite: "CREATE TYPE",
830
+ range: "CREATE TYPE",
686
831
  domain: "CREATE DOMAIN",
687
832
  extension: "CREATE EXTENSION",
688
833
  function: "CREATE FUNCTION",
689
834
  procedure: "CREATE PROCEDURE",
835
+ aggregate: "CREATE AGGREGATE",
690
836
  trigger: "CREATE TRIGGER",
837
+ eventTrigger: "CREATE EVENT TRIGGER",
838
+ collation: "CREATE COLLATION",
839
+ cast: "CREATE CAST",
691
840
  policy: "CREATE POLICY",
692
841
  role: "CREATE ROLE",
693
842
  grant: "GRANT",
@@ -715,6 +864,13 @@ export function stringValue(s: string | undefined): string | undefined {
715
864
  return s;
716
865
  }
717
866
 
867
+ /** A definition list's values, by option name: each value as written, interpolations rendered; an option with no value is `true`. */
868
+ function definitionValues(ctx: Ctx, elems: readonly DefElem[], path: string): Record<string, string> {
869
+ const out: Record<string, string> = {};
870
+ for (const e of elems) out[e.name] = e.value ? req(text(ctx, e.value, path)) : "true";
871
+ return out;
872
+ }
873
+
718
874
  /** A qualified name's schema and name. An interpolated schema names the schema; an interpolated object gives both. */
719
875
  function qualified(ctx: Ctx, n: NameNode): { schema?: string; name: string } {
720
876
  const pieces: string[] = [];
@@ -926,7 +1082,12 @@ function applyComments(
926
1082
  if (value !== undefined) constraint!.comment = value;
927
1083
  } else {
928
1084
  if (!objectTypes.includes(c.objectType)) refuse(c, `does not comment on the ${tag} this template declares`);
929
- if (c.objectType === "TRIGGER" || c.objectType === "POLICY") {
1085
+ if (c.objectType === "CAST") {
1086
+ // `COMMENT ON CAST (source AS target)`: the two types, as the CREATE CAST writes them.
1087
+ const loose = (x: string) => x.toLowerCase().replace(/\s+/g, " ").replace(/\s*([(),.])\s*/g, "$1");
1088
+ const named = `(${req(text(ctx, c.cast!.source))} AS ${req(text(ctx, c.cast!.target))})`;
1089
+ if (loose(named) !== loose(self.name)) refuse(c, `names another cast than ${self.name}`);
1090
+ } else if (c.objectType === "TRIGGER" || c.objectType === "POLICY") {
930
1091
  // `COMMENT ON TRIGGER name ON table`: the name is the trigger's, the table its own.
931
1092
  if (qualified(ctx, c.target).name !== self.name || !c.on || !isSelf({ ...qualified(ctx, c.on), name: self.name })) refuse(c, `names another ${c.objectType.toLowerCase()} than ${self.name}`);
932
1093
  } else if (!isSelf(qualified(ctx, c.target))) refuse(c, `names another object than ${sqlNameOf(self)}`);
@@ -1152,7 +1313,7 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
1152
1313
  throw templateSyntaxError(tag, parts, err);
1153
1314
  }
1154
1315
  const [node, ...rest] = nodes as [StatementNode, ...StatementNode[]];
1155
- if (node.statement !== STATEMENT_OF[tag]) {
1316
+ if (!STATEMENTS_OF[tag].includes(node.statement)) {
1156
1317
  const holds = STATEMENT_NAMES[node.statement];
1157
1318
  const use = TAG_OF[node.statement];
1158
1319
  throw new SqlTemplateError(
@@ -1194,9 +1355,9 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
1194
1355
  const dependsOn = [...new Set([...values, ...(joined?.refs ?? [])].filter((v) => isPostgresObject(v) || isColumnRef(v)))];
1195
1356
  const slots: TypedSlot[] = [];
1196
1357
  const unparsed = unparsedClauses(ctx, nodes.flatMap((n) => ("unknown" in n && n.unknown ? n.unknown : [])));
1197
- const make = <T extends PostgresObject>(type: PostgresEntityType, q: { schema?: string; name: string }, props: object, columnNames?: readonly string[]): T => {
1358
+ const make = <T extends PostgresObject>(type: PostgresEntityType, q: { schema?: string; name: string }, props: object, columnNames?: readonly string[], sqlName = sqlNameOf(q)): T => {
1198
1359
  if (unparsed) (props as { unparsed?: UnparsedClause[] }).unparsed = unparsed;
1199
- const entity = makeSqlEntity(PostgresObject.prototype as PostgresObject, type, sqlNameOf(q), props, columnNames, dependsOn) as T;
1360
+ const entity = makeSqlEntity(PostgresObject.prototype as PostgresObject, type, sqlName, props, columnNames, dependsOn) as T;
1200
1361
  const fed = ctx.fed.map((paths) => [...paths].sort());
1201
1362
  if (!joined) setInterpolationFields(entity, fed);
1202
1363
  else {
@@ -1239,6 +1400,99 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
1239
1400
  const props: EnumProps = stripUnset({ ...q, labels: node.labels.map((l) => req(stringValue(text(ctx, l, "labels")))), comment, ddl, source });
1240
1401
  return make(POSTGRES_ENTITY_TYPES.enum, q, props);
1241
1402
  }
1403
+ case "composite": {
1404
+ const q = qualified(ctx, node.name);
1405
+ const comment = applyComments(tag, ctx, comments, q, ["TYPE"]);
1406
+ const attributes = node.attributes.map((a) => {
1407
+ feed(ctx, a.nameSpan, "attributes");
1408
+ return stripUnset({ name: columnName(ctx, { name: a.name, span: a.nameSpan }), type: req(text(ctx, a.type, "attributes")), collate: text(ctx, a.collate, "attributes") }) as CompositeAttributeDef;
1409
+ });
1410
+ const props: CompositeTypeProps = strip({ ...q, attributes, comment, ddl, source }, ["attributes"]);
1411
+ return make(POSTGRES_ENTITY_TYPES.composite, q, props);
1412
+ }
1413
+ case "range": {
1414
+ const q = qualified(ctx, node.name);
1415
+ const comment = applyComments(tag, ctx, comments, q, ["TYPE"]);
1416
+ const opt = definitionValues(ctx, node.options, "options");
1417
+ const props: RangeTypeProps = stripUnset({
1418
+ ...q,
1419
+ subtype: req(opt.subtype),
1420
+ subtypeOpclass: opt.subtype_opclass,
1421
+ collation: opt.collation,
1422
+ canonical: opt.canonical,
1423
+ subtypeDiff: opt.subtype_diff,
1424
+ multirangeTypeName: opt.multirange_type_name,
1425
+ comment,
1426
+ ddl,
1427
+ source,
1428
+ });
1429
+ return make(POSTGRES_ENTITY_TYPES.range, q, props);
1430
+ }
1431
+ case "aggregate": {
1432
+ const q = qualified(ctx, node.name);
1433
+ const comment = applyComments(tag, ctx, comments, q, ["AGGREGATE"]);
1434
+ const arg = (a: (typeof node.args)[number]): AggregateArgDef => stripUnset({ mode: (a.mode?.toLowerCase() ?? "in") as AggregateArgDef["mode"], name: a.name, type: req(text(ctx, a.type, "args")) });
1435
+ const props: AggregateProps = strip(
1436
+ {
1437
+ ...q,
1438
+ orReplace: node.orReplace || undefined,
1439
+ args: node.args.map(arg),
1440
+ orderBy: node.orderBy?.map(arg),
1441
+ options: definitionValues(ctx, node.options, "options"),
1442
+ comment,
1443
+ ddl,
1444
+ source,
1445
+ },
1446
+ ["args"],
1447
+ );
1448
+ return make(POSTGRES_ENTITY_TYPES.aggregate, q, props);
1449
+ }
1450
+ case "collation": {
1451
+ const q = qualified(ctx, node.name);
1452
+ const comment = applyComments(tag, ctx, comments, q, ["COLLATION"]);
1453
+ const props: CollationProps = stripUnset({ ...q, ifNotExists: node.ifNotExists || undefined, options: definitionValues(ctx, node.options, "options"), comment, ddl, source });
1454
+ return make(POSTGRES_ENTITY_TYPES.collation, q, props);
1455
+ }
1456
+ case "cast": {
1457
+ const sourceType = req(text(ctx, node.source, "sourceType"));
1458
+ const targetType = req(text(ctx, node.target, "targetType"));
1459
+ const name = `(${sourceType} AS ${targetType})`;
1460
+ const comment = applyComments(tag, ctx, comments, { name }, ["CAST"]);
1461
+ const props: CastProps = stripUnset({
1462
+ name,
1463
+ sourceType,
1464
+ targetType,
1465
+ method: node.method,
1466
+ function: node.function ? target(ctx, node.function) : undefined,
1467
+ functionName: node.function ? targetName(ctx, node.function) : undefined,
1468
+ functionArgs: node.functionArgs ? text(ctx, node.functionArgs, "functionArgs") : undefined,
1469
+ context: node.context,
1470
+ comment,
1471
+ ddl,
1472
+ source,
1473
+ });
1474
+ return make(POSTGRES_ENTITY_TYPES.cast, { name }, props, undefined, name);
1475
+ }
1476
+ case "eventTrigger": {
1477
+ const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
1478
+ feed(ctx, node.name.span, "name");
1479
+ const comment = applyComments(tag, ctx, comments, { name }, ["EVENT TRIGGER"]);
1480
+ const tags = node.filters.flatMap((f) => f.values.map((v) => req(stringValue(text(ctx, v, "tags")))));
1481
+ const props: EventTriggerProps = strip(
1482
+ {
1483
+ name,
1484
+ event: node.event.name || identValue(req(text(ctx, node.event.span, "event"))),
1485
+ tags: tags.length > 0 ? tags : undefined,
1486
+ function: target(ctx, node.function),
1487
+ functionName: targetName(ctx, node.function),
1488
+ comment,
1489
+ ddl,
1490
+ source,
1491
+ },
1492
+ [],
1493
+ );
1494
+ return make(POSTGRES_ENTITY_TYPES.eventTrigger, { name }, props);
1495
+ }
1242
1496
  case "domain": {
1243
1497
  const q = qualified(ctx, node.name);
1244
1498
  const checks = node.checks.map((c) => stripUnset({ name: c.name, expr: req(text(ctx, c.expr, "checks")), notValid: c.notValid }) as CheckDef);
@@ -1644,9 +1898,13 @@ export function sequence(strings: TemplateStringsArray, ...values: unknown[]): P
1644
1898
  return build("sequence", strings, values) as PostgresSequence;
1645
1899
  }
1646
1900
 
1647
- /** `` type`CREATE TYPE name AS ENUM (...)` ``: one enum type. */
1648
- export function type(strings: TemplateStringsArray, ...values: unknown[]): PostgresEnum {
1649
- return build("type", strings, values) as PostgresEnum;
1901
+ /**
1902
+ * `` type`CREATE TYPE name AS ENUM (...)` ``: one enum type; `AS (attribute
1903
+ * type, ...)` a composite type, `AS RANGE (SUBTYPE = ...)` a range type (and
1904
+ * its multirange type, which Postgres creates with it).
1905
+ */
1906
+ export function type(strings: TemplateStringsArray, ...values: unknown[]): PostgresType {
1907
+ return build("type", strings, values) as PostgresType;
1650
1908
  }
1651
1909
 
1652
1910
  /** `` domain`CREATE DOMAIN ...` ``: one domain. */
@@ -1673,6 +1931,36 @@ export function procedure(strings: TemplateStringsArray, ...values: unknown[]):
1673
1931
  return build("procedure", strings, values) as PostgresProcedure;
1674
1932
  }
1675
1933
 
1934
+ /**
1935
+ * `` aggregate`CREATE [OR REPLACE] AGGREGATE name(argtype, ...) (SFUNC = ..., STYPE = ...)` ``:
1936
+ * one aggregate, identified, as a function is, by its argument types.
1937
+ */
1938
+ export function aggregate(strings: TemplateStringsArray, ...values: unknown[]): PostgresAggregate {
1939
+ return build("aggregate", strings, values) as PostgresAggregate;
1940
+ }
1941
+
1942
+ /** `` collation`CREATE COLLATION name (provider = icu, locale = '...')` ``: one collation. */
1943
+ export function collation(strings: TemplateStringsArray, ...values: unknown[]): PostgresCollation {
1944
+ return build("collation", strings, values) as PostgresCollation;
1945
+ }
1946
+
1947
+ /**
1948
+ * `` cast`CREATE CAST (source AS target) WITH FUNCTION ${fn}(source) [AS ASSIGNMENT | AS IMPLICIT]` ``:
1949
+ * one cast, named by its two types. Creating one takes owning the source or
1950
+ * the target type.
1951
+ */
1952
+ export function cast(strings: TemplateStringsArray, ...values: unknown[]): PostgresCast {
1953
+ return build("cast", strings, values) as PostgresCast;
1954
+ }
1955
+
1956
+ /**
1957
+ * `` eventTrigger`CREATE EVENT TRIGGER name ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION ${fn}()` ``:
1958
+ * one event trigger, the database's (no schema). Creating one takes a superuser.
1959
+ */
1960
+ export function eventTrigger(strings: TemplateStringsArray, ...values: unknown[]): PostgresEventTrigger {
1961
+ return build("eventTrigger", strings, values) as PostgresEventTrigger;
1962
+ }
1963
+
1676
1964
  /** `` trigger`CREATE [OR REPLACE] [CONSTRAINT] TRIGGER name ... ON ${table} ... EXECUTE FUNCTION ${fn}()` ``: one trigger on its table. */
1677
1965
  export function trigger(strings: TemplateStringsArray, ...values: unknown[]): PostgresTrigger {
1678
1966
  return build("trigger", strings, values) as PostgresTrigger;
@@ -11,7 +11,13 @@ export const POSTGRES_ENTITY_TYPES = {
11
11
  materializedView: "Postgres::MaterializedView",
12
12
  sequence: "Postgres::Sequence",
13
13
  enum: "Postgres::Enum",
14
+ composite: "Postgres::CompositeType",
15
+ range: "Postgres::RangeType",
14
16
  domain: "Postgres::Domain",
17
+ aggregate: "Postgres::Aggregate",
18
+ collation: "Postgres::Collation",
19
+ cast: "Postgres::Cast",
20
+ eventTrigger: "Postgres::EventTrigger",
15
21
  extension: "Postgres::Extension",
16
22
  function: "Postgres::Function",
17
23
  procedure: "Postgres::Procedure",