@intentius/chant-lexicon-sql 0.124.11 → 0.124.13

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 (173) hide show
  1. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  2. package/dist/clickhouse/entities.d.ts +7 -1
  3. package/dist/clickhouse/entities.d.ts.map +1 -1
  4. package/dist/clickhouse/expressions.d.ts +27 -0
  5. package/dist/clickhouse/expressions.d.ts.map +1 -0
  6. package/dist/clickhouse/parser.d.ts +12 -2
  7. package/dist/clickhouse/parser.d.ts.map +1 -1
  8. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  9. package/dist/clickhouse/plan/normalize.d.ts +11 -1
  10. package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
  11. package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
  12. package/dist/clickhouse/plan/rules.d.ts +1 -0
  13. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  14. package/dist/clickhouse.d.ts +1 -0
  15. package/dist/clickhouse.d.ts.map +1 -1
  16. package/dist/codegen/classifier-page.d.ts.map +1 -1
  17. package/dist/codegen/postgres-classifier-page.d.ts.map +1 -1
  18. package/dist/core/cursor.d.ts +41 -0
  19. package/dist/core/cursor.d.ts.map +1 -1
  20. package/dist/core/find-templates.d.ts +1 -1
  21. package/dist/core/find-templates.d.ts.map +1 -1
  22. package/dist/core/interpolation.d.ts +18 -1
  23. package/dist/core/interpolation.d.ts.map +1 -1
  24. package/dist/files/clickhouse.d.ts +2 -2
  25. package/dist/files/clickhouse.d.ts.map +1 -1
  26. package/dist/files/common.d.ts +16 -0
  27. package/dist/files/common.d.ts.map +1 -1
  28. package/dist/files/postgres.d.ts +2 -2
  29. package/dist/files/postgres.d.ts.map +1 -1
  30. package/dist/import-parser.d.ts +2 -1
  31. package/dist/import-parser.d.ts.map +1 -1
  32. package/dist/integrity.json +24 -24
  33. package/dist/lint/post-synth/clickhouse-helpers.d.ts +6 -19
  34. package/dist/lint/post-synth/clickhouse-helpers.d.ts.map +1 -1
  35. package/dist/lint/post-synth/postgres-helpers.d.ts +15 -55
  36. package/dist/lint/post-synth/postgres-helpers.d.ts.map +1 -1
  37. package/dist/lint/post-synth/postgres-names.d.ts +2 -2
  38. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  39. package/dist/lint/post-synth/sql-helpers.d.ts +5 -6
  40. package/dist/lint/post-synth/sql-helpers.d.ts.map +1 -1
  41. package/dist/lint/post-synth/sqlch102.d.ts.map +1 -1
  42. package/dist/lint/post-synth/sqlch103.d.ts.map +1 -1
  43. package/dist/lint/post-synth/sqlch104.d.ts.map +1 -1
  44. package/dist/lint/post-synth/sqlch105.d.ts.map +1 -1
  45. package/dist/lint/post-synth/sqlch106.d.ts.map +1 -1
  46. package/dist/lint/post-synth/sqlch107.d.ts.map +1 -1
  47. package/dist/lint/post-synth/sqlch111.d.ts.map +1 -1
  48. package/dist/lint/post-synth/sqlch112.d.ts.map +1 -1
  49. package/dist/lint/post-synth/sqlch113.d.ts.map +1 -1
  50. package/dist/lint/post-synth/sqlch114.d.ts.map +1 -1
  51. package/dist/lint/post-synth/sqlch115.d.ts.map +1 -1
  52. package/dist/lint/post-synth/sqlch120.d.ts.map +1 -1
  53. package/dist/lint/post-synth/sqlch125.d.ts.map +1 -1
  54. package/dist/lint/rules/postgres-templates.d.ts +1 -1
  55. package/dist/lint/rules/postgres-templates.d.ts.map +1 -1
  56. package/dist/lint/rules/templates.d.ts +1 -1
  57. package/dist/lint/rules/templates.d.ts.map +1 -1
  58. package/dist/lint/rules/typed-names.d.ts +1 -1
  59. package/dist/lint/rules/typed-names.d.ts.map +1 -1
  60. package/dist/manifest.json +1 -1
  61. package/dist/postgres/apply/statements.d.ts.map +1 -1
  62. package/dist/postgres/entities.d.ts +7 -1
  63. package/dist/postgres/entities.d.ts.map +1 -1
  64. package/dist/postgres/parser.d.ts +10 -2
  65. package/dist/postgres/parser.d.ts.map +1 -1
  66. package/dist/postgres/plan/diff.d.ts.map +1 -1
  67. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  68. package/dist/postgres/plan/rules.d.ts +1 -0
  69. package/dist/postgres/plan/rules.d.ts.map +1 -1
  70. package/dist/postgres.d.ts +1 -0
  71. package/dist/postgres.d.ts.map +1 -1
  72. package/dist/read/clickhouse.d.ts +66 -0
  73. package/dist/read/clickhouse.d.ts.map +1 -0
  74. package/dist/read/objects.d.ts +99 -0
  75. package/dist/read/objects.d.ts.map +1 -0
  76. package/dist/read/postgres.d.ts +83 -0
  77. package/dist/read/postgres.d.ts.map +1 -0
  78. package/dist/read.d.ts +49 -0
  79. package/dist/read.d.ts.map +1 -0
  80. package/dist/rules/clickhouse-helpers.ts +20 -97
  81. package/dist/rules/postgres-helpers.ts +18 -53
  82. package/dist/rules/postgres-names.ts +6 -6
  83. package/dist/rules/postgres-templates.ts +1 -1
  84. package/dist/rules/sql-helpers.ts +8 -28
  85. package/dist/rules/sqlch003.ts +1 -1
  86. package/dist/rules/sqlch102.ts +2 -3
  87. package/dist/rules/sqlch103.ts +2 -3
  88. package/dist/rules/sqlch104.ts +2 -3
  89. package/dist/rules/sqlch105.ts +2 -3
  90. package/dist/rules/sqlch106.ts +2 -3
  91. package/dist/rules/sqlch107.ts +2 -3
  92. package/dist/rules/sqlch111.ts +2 -3
  93. package/dist/rules/sqlch112.ts +2 -3
  94. package/dist/rules/sqlch113.ts +2 -3
  95. package/dist/rules/sqlch114.ts +2 -3
  96. package/dist/rules/sqlch115.ts +2 -3
  97. package/dist/rules/sqlch120.ts +2 -3
  98. package/dist/rules/sqlch125.ts +2 -3
  99. package/dist/rules/sqlpg002.ts +1 -1
  100. package/dist/rules/templates.ts +1 -1
  101. package/dist/rules/typed-names.ts +1 -1
  102. package/dist/serializer.d.ts +11 -1
  103. package/dist/serializer.d.ts.map +1 -1
  104. package/dist/sql-files.d.ts +17 -1
  105. package/dist/sql-files.d.ts.map +1 -1
  106. package/dist/sql-types.d.ts +3 -0
  107. package/dist/sql-types.d.ts.map +1 -1
  108. package/package.json +2 -3
  109. package/src/clickhouse/apply/statements.ts +2 -0
  110. package/src/clickhouse/entities.ts +8 -1
  111. package/src/clickhouse/expressions.ts +108 -0
  112. package/src/clickhouse/parser.ts +113 -31
  113. package/src/clickhouse/plan/diff.ts +19 -0
  114. package/src/clickhouse/plan/normalize.ts +27 -2
  115. package/src/clickhouse/plan/rebuild-handoff.ts +2 -1
  116. package/src/clickhouse/plan/rules.ts +7 -0
  117. package/src/clickhouse/testdata/server-ddl-baseline.json +67 -0
  118. package/src/clickhouse.ts +10 -0
  119. package/src/codegen/classifier-page.ts +2 -0
  120. package/src/codegen/postgres-classifier-page.ts +2 -0
  121. package/src/core/cursor.ts +96 -0
  122. package/src/core/find-templates.ts +1 -1
  123. package/src/core/interpolation.ts +32 -1
  124. package/src/files/clickhouse.ts +7 -5
  125. package/src/files/common.ts +23 -0
  126. package/src/files/postgres.ts +12 -8
  127. package/src/import-parser.ts +2 -1
  128. package/src/lint/post-synth/clickhouse-helpers.ts +20 -97
  129. package/src/lint/post-synth/postgres-helpers.ts +18 -53
  130. package/src/lint/post-synth/postgres-names.ts +6 -6
  131. package/src/lint/post-synth/sql-helpers.ts +8 -28
  132. package/src/lint/post-synth/sqlch102.ts +2 -3
  133. package/src/lint/post-synth/sqlch103.ts +2 -3
  134. package/src/lint/post-synth/sqlch104.ts +2 -3
  135. package/src/lint/post-synth/sqlch105.ts +2 -3
  136. package/src/lint/post-synth/sqlch106.ts +2 -3
  137. package/src/lint/post-synth/sqlch107.ts +2 -3
  138. package/src/lint/post-synth/sqlch111.ts +2 -3
  139. package/src/lint/post-synth/sqlch112.ts +2 -3
  140. package/src/lint/post-synth/sqlch113.ts +2 -3
  141. package/src/lint/post-synth/sqlch114.ts +2 -3
  142. package/src/lint/post-synth/sqlch115.ts +2 -3
  143. package/src/lint/post-synth/sqlch120.ts +2 -3
  144. package/src/lint/post-synth/sqlch125.ts +2 -3
  145. package/src/lint/rules/postgres-templates.ts +1 -1
  146. package/src/lint/rules/sqlch003.ts +1 -1
  147. package/src/lint/rules/sqlpg002.ts +1 -1
  148. package/src/lint/rules/templates.ts +1 -1
  149. package/src/lint/rules/typed-names.ts +1 -1
  150. package/src/lint/rules/typescript-7.test.ts +101 -0
  151. package/src/lsp/completions.ts +1 -1
  152. package/src/lsp/postgres-template.ts +1 -1
  153. package/src/lsp/postgres.ts +1 -1
  154. package/src/lsp/refs.ts +1 -1
  155. package/src/lsp/template.ts +1 -1
  156. package/src/postgres/apply/statements.ts +2 -0
  157. package/src/postgres/entities.ts +9 -1
  158. package/src/postgres/parser.ts +31 -8
  159. package/src/postgres/plan/diff.ts +18 -0
  160. package/src/postgres/plan/normalize.ts +12 -0
  161. package/src/postgres/plan/rules.ts +7 -0
  162. package/src/postgres/testdata/server-ddl-baseline.json +38 -0
  163. package/src/postgres.ts +12 -0
  164. package/src/read/clickhouse.ts +137 -0
  165. package/src/read/objects.ts +137 -0
  166. package/src/read/postgres.ts +180 -0
  167. package/src/read-policies.e2e.test.ts +144 -0
  168. package/src/read.test.ts +143 -0
  169. package/src/read.ts +69 -0
  170. package/src/serializer.ts +37 -1
  171. package/src/sql-files.ts +27 -6
  172. package/src/sql-types.ts +19 -2
  173. package/src/unknown-clauses.test.ts +227 -0
@@ -1,41 +1,26 @@
1
1
  /**
2
2
  * Reading the sql lexicon's build output in a post-synth check: the JSON
3
- * document the serializer writes (`dialect`, `applyOrder`, `objects`).
3
+ * document the serializer writes (`dialect`, `applyOrder`, `objects`). The
4
+ * documents are found by `../../read/objects.ts`, the same path the public
5
+ * read API (`@intentius/chant-lexicon-sql/read`) takes.
4
6
  */
5
7
 
6
8
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
9
+ import { dialectObjects, recordedPostgresMajor, type SqlObjectJson } from "../../read/objects";
7
10
 
8
- export interface OutputObject {
9
- export: string;
10
- type: string;
11
- name: string;
11
+ export interface OutputObject extends SqlObjectJson {
12
12
  database?: string;
13
13
  engine?: { name: string; args?: string[] };
14
- [key: string]: unknown;
15
14
  }
16
15
 
17
16
  /** Every schema object in the build's sql output, ClickHouse dialect. */
18
17
  export function clickhouseObjects(ctx: PostSynthContext): OutputObject[] {
19
- const out: OutputObject[] = [];
20
- for (const doc of ctx.docs ?? []) {
21
- if (doc.error || doc.lexicon !== "sql" || doc.format !== "json") continue;
22
- const value = doc.value as { dialect?: unknown; objects?: unknown };
23
- if (value?.dialect !== "clickhouse" || !Array.isArray(value.objects)) continue;
24
- out.push(...(value.objects as OutputObject[]));
25
- }
26
- return out;
18
+ return dialectObjects(ctx, "clickhouse") as OutputObject[];
27
19
  }
28
20
 
29
21
  /** Every schema object in the build's sql output, Postgres dialect. */
30
22
  export function postgresObjects(ctx: PostSynthContext): OutputObject[] {
31
- const out: OutputObject[] = [];
32
- for (const doc of ctx.docs ?? []) {
33
- if (doc.error || doc.lexicon !== "sql" || doc.format !== "json") continue;
34
- const value = doc.value as { dialect?: unknown; objects?: unknown };
35
- if (value?.dialect !== "postgres" || !Array.isArray(value.objects)) continue;
36
- out.push(...(value.objects as OutputObject[]));
37
- }
38
- return out;
23
+ return dialectObjects(ctx, "postgres") as OutputObject[];
39
24
  }
40
25
 
41
26
  /**
@@ -44,10 +29,5 @@ export function postgresObjects(ctx: PostSynthContext): OutputObject[] {
44
29
  * output that carries none.
45
30
  */
46
31
  export function postgresMajorOf(ctx: PostSynthContext, fallback: number): number {
47
- for (const doc of ctx.docs ?? []) {
48
- if (doc.error || doc.lexicon !== "sql" || doc.format !== "json") continue;
49
- const value = doc.value as { dialect?: unknown; postgresMajor?: unknown };
50
- if (value?.dialect === "postgres" && typeof value.postgresMajor === "number") return value.postgresMajor;
51
- }
52
- return fallback;
32
+ return recordedPostgresMajor(ctx, fallback);
53
33
  }
@@ -1,5 +1,4 @@
1
- import { checkOf, isTable, keyElements } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, keyElements, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /**
5
4
  * SQLCH102: a PRIMARY KEY that is not a prefix of ORDER BY.
@@ -9,7 +8,7 @@ import { clickhouseObjects } from "./sql-helpers";
9
8
  * the table at CREATE.
10
9
  */
11
10
  export const sqlch102 = checkOf({ id: "SQLCH102", description: "A PRIMARY KEY is not a prefix of ORDER BY" }, (ctx, report) => {
12
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
11
+ for (const t of tablesOf(ctx)) {
13
12
  if (!t.primaryKey || !t.orderBy) continue;
14
13
  const pk = keyElements(t.primaryKey).map((e) => e.replace(/\s+/g, ""));
15
14
  const ob = keyElements(t.orderBy).map((e) => e.replace(/\s+/g, ""));
@@ -1,5 +1,4 @@
1
- import { baseEngineName, checkOf, columnByName, engineArguments, isNullableType, isTable, typeFamily } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { baseEngineName, checkOf, columnByName, engineArguments, isNullableType, tablesOf, typeFamily } from "./clickhouse-helpers";
3
2
 
4
3
  const VERSION_TYPES = new Set(["UInt8", "UInt16", "UInt32", "UInt64", "UInt128", "UInt256", "Date", "DateTime", "DateTime64"]);
5
4
 
@@ -25,7 +24,7 @@ const SPECIAL: Record<string, Array<{ role: string; index: number; accepts: (fam
25
24
  * .../collapsingmergetree, .../versionedcollapsingmergetree (sign: Int8).
26
25
  */
27
26
  export const sqlch103 = checkOf({ id: "SQLCH103", description: "An engine's version, sign or is_deleted column has an unsupported type" }, (ctx, report) => {
28
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
27
+ for (const t of tablesOf(ctx)) {
29
28
  if (!t.engine) continue;
30
29
  const roles = SPECIAL[baseEngineName(t.engine)];
31
30
  if (!roles) continue;
@@ -1,5 +1,4 @@
1
- import { baseEngineName, checkOf, engineArguments, isTable, splitTop } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { baseEngineName, checkOf, engineArguments, splitTop, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /** The engine arguments that name columns, by base engine. */
5
4
  function columnArguments(engine: string, args: string[]): string[] {
@@ -26,7 +25,7 @@ function columnArguments(engine: string, args: string[]): string[] {
26
25
  * sign, version, is_deleted and summed columns are columns of the table).
27
26
  */
28
27
  export const sqlch104 = checkOf({ id: "SQLCH104", description: "A MergeTree engine argument names a column the table does not declare" }, (ctx, report) => {
29
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
28
+ for (const t of tablesOf(ctx)) {
30
29
  if (!t.engine || t.columns.length === 0) continue;
31
30
  const declared = new Set(t.columns.map((c) => c.name));
32
31
  for (const arg of columnArguments(baseEngineName(t.engine), engineArguments(t.engine))) {
@@ -1,5 +1,4 @@
1
- import { bareName, checkOf, isTable, keyElements } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { bareName, checkOf, keyElements, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /**
5
4
  * SQLCH105: ORDER BY, PRIMARY KEY, PARTITION BY or SAMPLE BY lists a bare
@@ -10,7 +9,7 @@ import { clickhouseObjects } from "./sql-helpers";
10
9
  * judged; an expression is left to the server.
11
10
  */
12
11
  export const sqlch105 = checkOf({ id: "SQLCH105", description: "A table key clause names a column the table does not declare" }, (ctx, report) => {
13
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
12
+ for (const t of tablesOf(ctx)) {
14
13
  if (t.columns.length === 0) continue;
15
14
  const declared = new Set(t.columns.map((c) => c.name));
16
15
  const clauses: Array<[string, string | undefined]> = [
@@ -1,5 +1,4 @@
1
- import { checkOf, isTable, mentionedNames } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, mentionedNames, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /**
5
4
  * SQLCH106: a data skipping index expression mentions a column the table does
@@ -11,7 +10,7 @@ import { clickhouseObjects } from "./sql-helpers";
11
10
  * SQL parser would guess.
12
11
  */
13
12
  export const sqlch106 = checkOf({ id: "SQLCH106", description: "A skip index expression names a column the table does not declare" }, (ctx, report) => {
14
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
13
+ for (const t of tablesOf(ctx)) {
15
14
  if (t.columns.length === 0) continue;
16
15
  const declared = new Set(t.columns.map((c) => c.name));
17
16
  for (const index of t.indexes ?? []) {
@@ -1,5 +1,4 @@
1
- import { bareName, checkOf, isTable, splitTop, typeFamily } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { bareName, checkOf, splitTop, tablesOf, typeFamily } from "./clickhouse-helpers";
3
2
 
4
3
  const DATE_TYPES = new Set(["Date", "Date32", "DateTime", "DateTime64"]);
5
4
  const TTL_ACTION = /\s+(DELETE|TO\s+DISK|TO\s+VOLUME|RECOMPRESS|GROUP\s+BY|WHERE)\b[\s\S]*$/i;
@@ -13,7 +12,7 @@ const TTL_ACTION = /\s+(DELETE|TO\s+DISK|TO\s+VOLUME|RECOMPRESS|GROUP\s+BY|WHERE
13
12
  * a function call is left to the server.
14
13
  */
15
14
  export const sqlch107 = checkOf({ id: "SQLCH107", description: "A TTL expression is built on a column that is not a Date or DateTime" }, (ctx, report) => {
16
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
15
+ for (const t of tablesOf(ctx)) {
17
16
  const ttls: Array<{ where: string; expr: string }> = [];
18
17
  if (t.ttl) for (const clause of splitTop(t.ttl)) ttls.push({ where: "table TTL", expr: clause.replace(TTL_ACTION, "") });
19
18
  for (const c of t.columns) if (c.ttl) ttls.push({ where: `column ${c.name} TTL`, expr: c.ttl.replace(TTL_ACTION, "") });
@@ -1,5 +1,4 @@
1
- import { checkOf, isTable, splitTop } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, splitTop, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  const MAX_VALUES = 100;
5
4
 
@@ -23,7 +22,7 @@ function allowedValues(expr: string): { column: string; count: number } | undefi
23
22
  * https://clickhouse.com/docs/best-practices/select-data-types.
24
23
  */
25
24
  export const sqlch111 = checkOf({ id: "SQLCH111", description: "A String column limited to a few values is not LowCardinality" }, (ctx, report) => {
26
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
25
+ for (const t of tablesOf(ctx)) {
27
26
  for (const c of t.constraints ?? []) {
28
27
  if (c.kind !== "CHECK") continue;
29
28
  const found = allowedValues(c.expr);
@@ -1,5 +1,4 @@
1
- import { checkOf, isTable, typeFamily } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, tablesOf, typeFamily } from "./clickhouse-helpers";
3
2
 
4
3
  const FINE_FUNCTIONS =
5
4
  /\b(toStartOfSecond|toStartOfMinute|toStartOfFiveMinutes?|toStartOfTenMinutes|toStartOfFifteenMinutes|toStartOfHour|toRelativeSecondNum|toRelativeMinuteNum|toRelativeHourNum|toUnixTimestamp|toYYYYMMDDhhmmss|toStartOfInterval)\s*\(/;
@@ -15,7 +14,7 @@ const FINE_FUNCTIONS =
15
14
  * the sub-day truncation functions.
16
15
  */
17
16
  export const sqlch112 = checkOf({ id: "SQLCH112", description: "A PARTITION BY expression is finer than a day" }, (ctx, report) => {
18
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
17
+ for (const t of tablesOf(ctx)) {
19
18
  const expr = t.partitionBy?.trim();
20
19
  if (!expr) continue;
21
20
  const col = t.columns.find((c) => c.name === expr.replace(/^\(|\)$/g, "").trim());
@@ -1,5 +1,4 @@
1
- import { checkOf, isMergeTree, isTable } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, isMergeTree, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /**
5
4
  * SQLCH113: a MergeTree table with neither ORDER BY nor PRIMARY KEY.
@@ -9,7 +8,7 @@ import { clickhouseObjects } from "./sql-helpers";
9
8
  * `ORDER BY tuple()` to have none). The server refuses the CREATE.
10
9
  */
11
10
  export const sqlch113 = checkOf({ id: "SQLCH113", description: "A MergeTree table declares no sort key" }, (ctx, report) => {
12
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
11
+ for (const t of tablesOf(ctx)) {
13
12
  if (!isMergeTree(t.engine) || t.orderBy || t.primaryKey) continue;
14
13
  report({
15
14
  severity: "error",
@@ -1,6 +1,5 @@
1
1
  import { CODECS } from "../../generated/clickhouse";
2
- import { checkOf, codecNames, isTable } from "./clickhouse-helpers";
3
- import { clickhouseObjects } from "./sql-helpers";
2
+ import { checkOf, codecNames, tablesOf } from "./clickhouse-helpers";
4
3
 
5
4
  /**
6
5
  * SQLCH114: a column CODEC names a codec the pinned server does not have.
@@ -10,7 +9,7 @@ import { clickhouseObjects } from "./sql-helpers";
10
9
  */
11
10
  export const sqlch114 = checkOf({ id: "SQLCH114", description: "A column codec is not one the pinned server has" }, (ctx, report) => {
12
11
  const known = new Set(Object.keys(CODECS).map((n) => n.toLowerCase()));
13
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
12
+ for (const t of tablesOf(ctx)) {
14
13
  for (const c of t.columns) {
15
14
  if (!c.codec) continue;
16
15
  for (const name of codecNames(c.codec)) {
@@ -1,6 +1,5 @@
1
1
  import { CODECS } from "../../generated/clickhouse";
2
- import { checkOf, codecNames, isTable } from "./clickhouse-helpers";
3
- import { clickhouseObjects } from "./sql-helpers";
2
+ import { checkOf, codecNames, tablesOf } from "./clickhouse-helpers";
4
3
 
5
4
  const SENSITIVE = /(^|_)(password|passwd|pwd|secret|api_?key|access_?key|private_?key|token|ssn|social_?security|credit_?card|card_?number|iban)(_|$)/i;
6
5
 
@@ -17,7 +16,7 @@ const SENSITIVE = /(^|_)(password|passwd|pwd|secret|api_?key|access_?key|private
17
16
  */
18
17
  export const sqlch115 = checkOf({ id: "SQLCH115", description: "A secret- or PII-named column carries no comment, TTL or encryption" }, (ctx, report) => {
19
18
  const encryption = new Set(Object.entries(CODECS).filter(([, s]) => s.encryption).map(([n]) => n.toLowerCase()));
20
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
19
+ for (const t of tablesOf(ctx)) {
21
20
  for (const c of t.columns) {
22
21
  if (!SENSITIVE.test(c.name) || c.comment || c.ttl || t.ttl) continue;
23
22
  if (c.codec && codecNames(c.codec).some((n) => encryption.has(n.toLowerCase()))) continue;
@@ -1,5 +1,4 @@
1
- import { checkOf, isTable } from "./clickhouse-helpers";
2
- import { clickhouseObjects } from "./sql-helpers";
1
+ import { checkOf, tablesOf } from "./clickhouse-helpers";
3
2
 
4
3
  /**
5
4
  * SQLCH120: a MergeTree engine written with the legacy positional arguments.
@@ -10,7 +9,7 @@ import { clickhouseObjects } from "./sql-helpers";
10
9
  * syntax is a bare `MergeTree` with ORDER BY, PARTITION BY and SETTINGS.
11
10
  */
12
11
  export const sqlch120 = checkOf({ id: "SQLCH120", description: "A MergeTree engine uses the deprecated positional arguments" }, (ctx, report) => {
13
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
12
+ for (const t of tablesOf(ctx)) {
14
13
  if (t.engine?.name !== "MergeTree" || (t.engine.args ?? []).length === 0) continue;
15
14
  report({
16
15
  severity: "warning",
@@ -1,6 +1,5 @@
1
1
  import { CODECS, CLICKHOUSE_VERSION } from "../../generated/clickhouse";
2
- import { checkOf, isTable, splitTop } from "./clickhouse-helpers";
3
- import { clickhouseObjects } from "./sql-helpers";
2
+ import { checkOf, splitTop, tablesOf } from "./clickhouse-helpers";
4
3
 
5
4
  type Param = { name: string; kind: string; optional?: boolean; range?: readonly [number, number]; values?: readonly string[] };
6
5
 
@@ -17,7 +16,7 @@ const NUMBER = /^-?\d+(\.\d+)?$/;
17
16
  */
18
17
  export const sqlch125 = checkOf({ id: "SQLCH125", description: "A codec parameter is outside what the codec takes" }, (ctx, report) => {
19
18
  const codecs = CODECS as Record<string, { parameters: readonly Param[] | "unknown" } | undefined>;
20
- for (const t of clickhouseObjects(ctx).filter(isTable)) {
19
+ for (const t of tablesOf(ctx)) {
21
20
  for (const c of t.columns) {
22
21
  if (!c.codec) continue;
23
22
  for (const part of splitTop(c.codec)) {
@@ -6,7 +6,7 @@
6
6
  * ClickHouse's).
7
7
  */
8
8
 
9
- import type * as ts from "typescript";
9
+ import type { ts } from "@intentius/chant/lint/typescript";
10
10
  import { tokenize, type Token } from "../../postgres/tokens";
11
11
  import type { PostgresTag } from "../../postgres/entities";
12
12
  import { findSqlTemplates, type FoundTemplate, type TemplateSource } from "../../core/find-templates";
@@ -1,4 +1,4 @@
1
- import * as ts from "typescript";
1
+ import { ts } from "@intentius/chant/lint/typescript";
2
2
  import type { LintContext, LintDiagnostic, LintRule } from "@intentius/chant/lint/rule";
3
3
  import { findTemplates } from "./templates";
4
4
 
@@ -1,4 +1,4 @@
1
- import * as ts from "typescript";
1
+ import { ts } from "@intentius/chant/lint/typescript";
2
2
  import type { LintContext, LintDiagnostic, LintRule } from "@intentius/chant/lint/rule";
3
3
  import { findPostgresTemplates as findTemplates } from "./postgres-templates";
4
4
 
@@ -7,7 +7,7 @@
7
7
  * because of the module it is imported from.
8
8
  */
9
9
 
10
- import type * as ts from "typescript";
10
+ import type { ts } from "@intentius/chant/lint/typescript";
11
11
  import { tokenize, type Token } from "../../clickhouse/tokens";
12
12
  import { findSqlTemplates, type FoundTemplate as SqlFoundTemplate, type TemplateSource } from "../../core/find-templates";
13
13
 
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { existsSync, readFileSync } from "node:fs";
13
13
  import { dirname, join, resolve } from "node:path";
14
- import * as ts from "typescript";
14
+ import { ts } from "@intentius/chant/lint/typescript";
15
15
 
16
16
  /** The generated file's name, beside the source (`chant sql types`). */
17
17
  export const SQL_TYPES_FILE = "sql-types.d.ts";
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The sql lint rules run in a project whose `typescript` is TypeScript 7
3
+ * (chant #3864).
4
+ *
5
+ * TypeScript 7's `typescript` package has no JS compiler API. chant core keeps
6
+ * its own 5.x and parses every file with it, but a rule that imported
7
+ * `typescript` itself got the project's copy, and every rule threw
8
+ * `ts.isImportDeclaration is not a function`, so `chant graph` refused the
9
+ * project. The rules now take `ts` from `@intentius/chant/lint/typescript`.
10
+ *
11
+ * In this repo the rules and core resolve the same `typescript`, so the test
12
+ * lays out the install a user gets: a project whose `node_modules/typescript`
13
+ * is TypeScript 7 (the root's `typescript-native` alias), a copy of this
14
+ * lexicon in it (a copy, because module resolution follows the real path),
15
+ * and chant core keeping its own TypeScript 5. It then runs the rules the way
16
+ * the CLI does, in a separate node process under tsx, over the examples.
17
+ */
18
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
19
+ import { execFileSync } from "node:child_process";
20
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ const lexiconDir = fileURLToPath(new URL("../../../", import.meta.url));
26
+ const repoRoot = join(lexiconDir, "..", "..");
27
+ const nodeModules = join(repoRoot, "node_modules");
28
+ const ts7 = join(nodeModules, "typescript-native");
29
+ const tsxLoader = join(nodeModules, "tsx", "dist", "loader.mjs");
30
+
31
+ const examples = ["postgres-getting-started", "getting-started"].map((name) => join(lexiconDir, "examples", name, "src"));
32
+
33
+ /** Runs in the child: every rule over every example file, reporting what threw. */
34
+ const RUNNER = `
35
+ import { createRequire } from "node:module";
36
+ import { readdirSync } from "node:fs";
37
+ import { join } from "node:path";
38
+ import { parseFile } from "@intentius/chant/lint/parser";
39
+ import { rules } from "@intentius/chant-lexicon-sql/lint/rules/index";
40
+
41
+ // The \`typescript\` the lexicon would get from a bare import.
42
+ const lexiconTs = createRequire(process.argv[3])("typescript/package.json").version;
43
+ const threw = [];
44
+ let checked = 0;
45
+ for (const dir of JSON.parse(process.argv[2])) {
46
+ for (const name of readdirSync(dir).filter((f) => f.endsWith(".ts"))) {
47
+ const filePath = join(dir, name);
48
+ const sourceFile = parseFile(filePath);
49
+ for (const rule of rules) {
50
+ try {
51
+ rule.check({ sourceFile, entities: [], filePath });
52
+ checked++;
53
+ } catch (err) {
54
+ threw.push(rule.id + " " + name + ": " + (err instanceof Error ? err.message : String(err)));
55
+ }
56
+ }
57
+ }
58
+ }
59
+ console.log(JSON.stringify({ lexiconTs, checked, threw }));
60
+ `;
61
+
62
+ const ready = existsSync(ts7) && existsSync(tsxLoader);
63
+
64
+ describe.skipIf(!ready)("sql lint rules in a TypeScript 7 project (#3864)", () => {
65
+ let project: string;
66
+ let lexiconPackageJson: string;
67
+
68
+ beforeAll(() => {
69
+ project = mkdtempSync(join(tmpdir(), "chant-sql-ts7-"));
70
+ const modules = join(project, "node_modules");
71
+ mkdirSync(join(modules, "@intentius"), { recursive: true });
72
+ symlinkSync(ts7, join(modules, "typescript"), "dir");
73
+ symlinkSync(join(repoRoot, "packages", "core"), join(modules, "@intentius", "chant"), "dir");
74
+ for (const dep of ["pg", "zod"]) symlinkSync(join(nodeModules, dep), join(modules, dep), "dir");
75
+ const copy = join(modules, "@intentius", "chant-lexicon-sql");
76
+ lexiconPackageJson = join(copy, "package.json");
77
+ cpSync(join(lexiconDir, "src"), join(copy, "src"), {
78
+ recursive: true,
79
+ filter: (src) => !src.endsWith(".test.ts"),
80
+ });
81
+ cpSync(join(lexiconDir, "package.json"), join(copy, "package.json"));
82
+ writeFileSync(join(project, "run.mts"), RUNNER);
83
+ });
84
+
85
+ afterAll(() => {
86
+ if (project) rmSync(project, { recursive: true, force: true });
87
+ });
88
+
89
+ test("no rule throws, and the lexicon really sees TypeScript 7", () => {
90
+ for (const dir of examples) expect(readdirSync(dir).length).toBeGreaterThan(0);
91
+ const out = execFileSync(process.execPath, ["--import", `file://${tsxLoader}`, join(project, "run.mts"), JSON.stringify(examples), lexiconPackageJson], {
92
+ cwd: project,
93
+ encoding: "utf-8",
94
+ env: { ...process.env, TSX_DISABLE_CACHE: "1" },
95
+ });
96
+ const { lexiconTs, checked, threw } = JSON.parse(out.trim().split("\n").pop()!);
97
+ expect(lexiconTs).toMatch(/^7\./);
98
+ expect(threw).toEqual([]);
99
+ expect(checked).toBeGreaterThan(0);
100
+ }, 120_000);
101
+ });
@@ -1,4 +1,4 @@
1
- import * as ts from "typescript";
1
+ import { ts } from "@intentius/chant/lint/typescript";
2
2
  import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
3
3
  import { lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
4
4
  import { catalogIndex, type CatalogIndex } from "./catalog";
@@ -4,7 +4,7 @@
4
4
  * rules, so the editor and `chant lint` agree on which tags are Postgres's.
5
5
  */
6
6
 
7
- import * as ts from "typescript";
7
+ import { ts } from "@intentius/chant/lint/typescript";
8
8
  import { isTrivia, tokenize, type Token } from "../postgres/tokens";
9
9
  import { parseStatements, type Span } from "../postgres/parser";
10
10
  import type { PostgresTag } from "../postgres/entities";
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, readFileSync } from "fs";
12
12
  import { dirname, resolve } from "path";
13
- import * as ts from "typescript";
13
+ import { ts } from "@intentius/chant/lint/typescript";
14
14
  import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
15
15
  import {
16
16
  describeRange,
package/src/lsp/refs.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { existsSync, readFileSync } from "fs";
8
8
  import { dirname, resolve } from "path";
9
- import * as ts from "typescript";
9
+ import { ts } from "@intentius/chant/lint/typescript";
10
10
  import { declarations, type Declared } from "./template";
11
11
 
12
12
  function importedFrom(content: string, local: string, fileName: string): { declared: Declared; file: string } | undefined {
@@ -5,7 +5,7 @@
5
5
  * is and which module's tags count.
6
6
  */
7
7
 
8
- import * as ts from "typescript";
8
+ import { ts } from "@intentius/chant/lint/typescript";
9
9
  import { isTrivia, tokenize, type Token } from "../clickhouse/tokens";
10
10
  import { parseCreate, type Span } from "../clickhouse/parser";
11
11
  import { CLICKHOUSE_TEMPLATE_SOURCE, findTemplates, type FoundTemplate, type SqlTag } from "../lint/rules/templates";
@@ -789,6 +789,8 @@ export function refusalDetail(changes: readonly PgChange[], name: string, declar
789
789
  ? `Run it as the expand-and-contract migration Op: ${ops.join(", ")} from @intentius/chant-lexicon-sql/postgres (add the new column, write both, backfill, switch readers, then drop the old).` +
790
790
  (ops.length < changes.length ? " The other changes have no migration Op yet: make them by hand as expand and contract." : "")
791
791
  : "No migration Op makes this change yet: make it by hand as expand and contract (add the new, write both, backfill, move readers, then drop the old).";
792
+ // A clause chant does not read (#3872) is refused for review: what the change takes is not known.
793
+ if (changes.every((c) => c.rule === "SQLPG299")) return `${name} needs review, since chant does not read the clause, so nothing was sent for it. ${lines.join(" ")}`;
792
794
  return `${name} needs expand and contract, which no in-place statement makes, so nothing was sent for it. ${lines.join(" ")} ${instead}`;
793
795
  }
794
796
 
@@ -58,7 +58,7 @@ import { setInterpolationFields } from "@intentius/chant/provenance";
58
58
  import { isTrivia, POSTGRES_LEXICAL, SqlSyntaxError, untokenize, type Token } from "./tokens";
59
59
  import { identValue, parseStatements, type ColumnNode, type CommentNode, type ConstraintNode, type GrantNode, type NameNode, type RowSecurityNode, type Span, type StatementNode } from "./parser";
60
60
  import { keywordCategory, quoteIdent } from "./keywords";
61
- import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, type TemplateCtx, type TemplateDialect } from "../core/interpolation";
61
+ import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, unparsedClauses, type TemplateCtx, type TemplateDialect, type UnparsedClause } from "../core/interpolation";
62
62
  import { SqlObject, isColumnRefOf, isSqlObjectOf, makeSqlEntity } from "../core/entity";
63
63
  import type { LineageEdge } from "../core/references";
64
64
  import { SqlLiteral, SqlTemplateError, describeValue as describe, stripUnset, templateParts } from "../core/template";
@@ -66,6 +66,7 @@ import { setTypedSlots, type ColumnsIn, type Named, type RowIn, type SlotsIn, ty
66
66
 
67
67
  export { SQL_LEXICON } from "../core/entity";
68
68
  export type { LineageEdge } from "../core/references";
69
+ export type { UnparsedClause } from "../core/interpolation";
69
70
  export { SqlLiteral, SqlTemplateError } from "../core/template";
70
71
  export { quoteIdent } from "./keywords";
71
72
  export type { AnyColumn, Column, ColumnTraits, KeyColumn, Named, NotNullColumn, UncheckedColumn, Unnamed } from "../core/typed";
@@ -155,6 +156,11 @@ interface CommonProps {
155
156
  ddl: string;
156
157
  /** The template's raw parts, as written, for a source round trip. */
157
158
  source: { strings: string[] };
159
+ /**
160
+ * Clauses chant's parser does not know (#3872), kept in `ddl` as written
161
+ * and left to the catalog checks and the server. Absent when there are none.
162
+ */
163
+ unparsed?: UnparsedClause[];
158
164
  }
159
165
 
160
166
  export interface SchemaProps extends Omit<CommonProps, "schema"> {
@@ -1182,7 +1188,9 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
1182
1188
  const source = { strings: rawParts };
1183
1189
  const dependsOn = [...new Set([...values, ...(joined?.refs ?? [])].filter((v) => isPostgresObject(v) || isColumnRef(v)))];
1184
1190
  const slots: TypedSlot[] = [];
1191
+ const unparsed = unparsedClauses(ctx, nodes.flatMap((n) => ("unknown" in n && n.unknown ? n.unknown : [])));
1185
1192
  const make = <T extends PostgresObject>(type: PostgresEntityType, q: { schema?: string; name: string }, props: object, columnNames?: readonly string[]): T => {
1193
+ if (unparsed) (props as { unparsed?: UnparsedClause[] }).unparsed = unparsed;
1186
1194
  const entity = makeSqlEntity(PostgresObject.prototype as PostgresObject, type, sqlNameOf(q), props, columnNames, dependsOn) as T;
1187
1195
  const fed = ctx.fed.map((paths) => [...paths].sort());
1188
1196
  if (!joined) setInterpolationFields(entity, fed);