@intentius/chant-lexicon-sql 0.121.1 → 0.122.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/clickhouse/catalog-types.d.ts +8 -0
  2. package/dist/clickhouse/catalog-types.d.ts.map +1 -1
  3. package/dist/clickhouse/overlays/codecs.d.ts.map +1 -1
  4. package/dist/clickhouse/overlays/kinds.d.ts +1 -1
  5. package/dist/clickhouse/overlays/kinds.d.ts.map +1 -1
  6. package/dist/clickhouse/overlays/types.d.ts.map +1 -1
  7. package/dist/clickhouse/type-syntax.d.ts +33 -0
  8. package/dist/clickhouse/type-syntax.d.ts.map +1 -0
  9. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  10. package/dist/codegen/postgres-module.d.ts.map +1 -1
  11. package/dist/generated/clickhouse.d.ts +7 -1
  12. package/dist/generated/clickhouse.d.ts.map +1 -1
  13. package/dist/generated/postgres.d.ts +6 -0
  14. package/dist/generated/postgres.d.ts.map +1 -1
  15. package/dist/integrity.json +23 -5
  16. package/dist/lint/audit-catalog.d.ts.map +1 -1
  17. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  18. package/dist/lint/post-synth/clickhouse-type-check.d.ts +37 -0
  19. package/dist/lint/post-synth/clickhouse-type-check.d.ts.map +1 -0
  20. package/dist/lint/post-synth/index.d.ts.map +1 -1
  21. package/dist/lint/post-synth/postgres-audit-entries.d.ts.map +1 -1
  22. package/dist/lint/post-synth/postgres-names.d.ts +149 -0
  23. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -0
  24. package/dist/lint/post-synth/sql101.d.ts +9 -0
  25. package/dist/lint/post-synth/sql101.d.ts.map +1 -0
  26. package/dist/lint/post-synth/sqlch121.d.ts +10 -0
  27. package/dist/lint/post-synth/sqlch121.d.ts.map +1 -0
  28. package/dist/lint/post-synth/sqlch122.d.ts +10 -0
  29. package/dist/lint/post-synth/sqlch122.d.ts.map +1 -0
  30. package/dist/lint/post-synth/sqlch123.d.ts +9 -0
  31. package/dist/lint/post-synth/sqlch123.d.ts.map +1 -0
  32. package/dist/lint/post-synth/sqlch124.d.ts +6 -0
  33. package/dist/lint/post-synth/sqlch124.d.ts.map +1 -0
  34. package/dist/lint/post-synth/sqlch125.d.ts +11 -0
  35. package/dist/lint/post-synth/sqlch125.d.ts.map +1 -0
  36. package/dist/lint/post-synth/sqlch126.d.ts +12 -0
  37. package/dist/lint/post-synth/sqlch126.d.ts.map +1 -0
  38. package/dist/lint/post-synth/sqlch127.d.ts +22 -0
  39. package/dist/lint/post-synth/sqlch127.d.ts.map +1 -0
  40. package/dist/lint/post-synth/sqlpg119.d.ts +18 -0
  41. package/dist/lint/post-synth/sqlpg119.d.ts.map +1 -0
  42. package/dist/lint/post-synth/sqlpg120.d.ts +17 -0
  43. package/dist/lint/post-synth/sqlpg120.d.ts.map +1 -0
  44. package/dist/lint/post-synth/sqlpg121.d.ts +12 -0
  45. package/dist/lint/post-synth/sqlpg121.d.ts.map +1 -0
  46. package/dist/lint/post-synth/sqlpg122.d.ts +12 -0
  47. package/dist/lint/post-synth/sqlpg122.d.ts.map +1 -0
  48. package/dist/lint/post-synth/sqlpg123.d.ts +17 -0
  49. package/dist/lint/post-synth/sqlpg123.d.ts.map +1 -0
  50. package/dist/lint/post-synth/sqlpg124.d.ts +16 -0
  51. package/dist/lint/post-synth/sqlpg124.d.ts.map +1 -0
  52. package/dist/lint/post-synth/sqlpg125.d.ts +17 -0
  53. package/dist/lint/post-synth/sqlpg125.d.ts.map +1 -0
  54. package/dist/lint/post-synth/sqlpg126.d.ts +19 -0
  55. package/dist/lint/post-synth/sqlpg126.d.ts.map +1 -0
  56. package/dist/manifest.json +1 -1
  57. package/dist/okf/index.md +16 -0
  58. package/dist/okf/rules/SQL101.md +23 -0
  59. package/dist/okf/rules/SQLCH121.md +11 -0
  60. package/dist/okf/rules/SQLCH122.md +11 -0
  61. package/dist/okf/rules/SQLCH123.md +11 -0
  62. package/dist/okf/rules/SQLCH124.md +18 -0
  63. package/dist/okf/rules/SQLCH125.md +11 -0
  64. package/dist/okf/rules/SQLCH126.md +15 -0
  65. package/dist/okf/rules/SQLCH127.md +15 -0
  66. package/dist/okf/rules/SQLPG119.md +16 -0
  67. package/dist/okf/rules/SQLPG120.md +16 -0
  68. package/dist/okf/rules/SQLPG121.md +11 -0
  69. package/dist/okf/rules/SQLPG122.md +15 -0
  70. package/dist/okf/rules/SQLPG123.md +16 -0
  71. package/dist/okf/rules/SQLPG124.md +11 -0
  72. package/dist/okf/rules/SQLPG125.md +11 -0
  73. package/dist/okf/rules/SQLPG126.md +11 -0
  74. package/dist/okf/types/Dictionary.md +4 -0
  75. package/dist/okf/types/Function.md +5 -0
  76. package/dist/okf/types/Grant.md +6 -0
  77. package/dist/okf/types/MaterializedView.md +1 -0
  78. package/dist/okf/types/PostgresDefaultPrivileges.md +4 -0
  79. package/dist/okf/types/PostgresDomain.md +2 -0
  80. package/dist/okf/types/PostgresEnum.md +1 -0
  81. package/dist/okf/types/PostgresFunction.md +4 -0
  82. package/dist/okf/types/PostgresGrant.md +5 -0
  83. package/dist/okf/types/PostgresPolicy.md +4 -0
  84. package/dist/okf/types/PostgresProcedure.md +4 -0
  85. package/dist/okf/types/PostgresSequence.md +2 -0
  86. package/dist/okf/types/PostgresTrigger.md +4 -0
  87. package/dist/okf/types/RowPolicy.md +4 -0
  88. package/dist/okf/types/Table.md +1 -0
  89. package/dist/okf/types/View.md +1 -0
  90. package/dist/postgres/overlays/extension-objects.d.ts +28 -0
  91. package/dist/postgres/overlays/extension-objects.d.ts.map +1 -0
  92. package/dist/postgres/overlays/types.d.ts +10 -0
  93. package/dist/postgres/overlays/types.d.ts.map +1 -1
  94. package/dist/rules/audit-entries.ts +7 -0
  95. package/dist/rules/clickhouse-type-check.ts +173 -0
  96. package/dist/rules/postgres-audit-entries.ts +8 -0
  97. package/dist/rules/postgres-names.ts +506 -0
  98. package/dist/rules/sql101.ts +62 -0
  99. package/dist/rules/sqlch121.ts +12 -0
  100. package/dist/rules/sqlch122.ts +12 -0
  101. package/dist/rules/sqlch123.ts +11 -0
  102. package/dist/rules/sqlch124.ts +21 -0
  103. package/dist/rules/sqlch125.ts +56 -0
  104. package/dist/rules/sqlch126.ts +57 -0
  105. package/dist/rules/sqlch127.ts +128 -0
  106. package/dist/rules/sqlpg119.ts +37 -0
  107. package/dist/rules/sqlpg120.ts +70 -0
  108. package/dist/rules/sqlpg121.ts +32 -0
  109. package/dist/rules/sqlpg122.ts +28 -0
  110. package/dist/rules/sqlpg123.ts +63 -0
  111. package/dist/rules/sqlpg124.ts +47 -0
  112. package/dist/rules/sqlpg125.ts +40 -0
  113. package/dist/rules/sqlpg126.ts +37 -0
  114. package/package.json +2 -2
  115. package/src/clickhouse/catalog-types.ts +9 -0
  116. package/src/clickhouse/overlays/codecs.ts +6 -2
  117. package/src/clickhouse/overlays/kinds.ts +1 -1
  118. package/src/clickhouse/overlays/types.ts +20 -3
  119. package/src/clickhouse/type-syntax.ts +82 -0
  120. package/src/codegen/clickhouse-module.ts +15 -1
  121. package/src/codegen/postgres-module.ts +43 -0
  122. package/src/composites/composites.test.ts +7 -6
  123. package/src/generated/clickhouse.ts +1905 -6
  124. package/src/generated/postgres.ts +2639 -0
  125. package/src/lint/audit-catalog.ts +8 -0
  126. package/src/lint/post-synth/audit-entries.ts +7 -0
  127. package/src/lint/post-synth/clickhouse-catalog-checks.test.ts +315 -0
  128. package/src/lint/post-synth/clickhouse-type-check.ts +173 -0
  129. package/src/lint/post-synth/index.ts +32 -0
  130. package/src/lint/post-synth/postgres-audit-entries.ts +8 -0
  131. package/src/lint/post-synth/postgres-checks.test.ts +214 -1
  132. package/src/lint/post-synth/postgres-names.ts +506 -0
  133. package/src/lint/post-synth/sql101.ts +62 -0
  134. package/src/lint/post-synth/sqlch121.ts +12 -0
  135. package/src/lint/post-synth/sqlch122.ts +12 -0
  136. package/src/lint/post-synth/sqlch123.ts +11 -0
  137. package/src/lint/post-synth/sqlch124.ts +21 -0
  138. package/src/lint/post-synth/sqlch125.ts +56 -0
  139. package/src/lint/post-synth/sqlch126.ts +57 -0
  140. package/src/lint/post-synth/sqlch127.ts +128 -0
  141. package/src/lint/post-synth/sqlpg119.ts +37 -0
  142. package/src/lint/post-synth/sqlpg120.ts +70 -0
  143. package/src/lint/post-synth/sqlpg121.ts +32 -0
  144. package/src/lint/post-synth/sqlpg122.ts +28 -0
  145. package/src/lint/post-synth/sqlpg123.ts +63 -0
  146. package/src/lint/post-synth/sqlpg124.ts +47 -0
  147. package/src/lint/post-synth/sqlpg125.ts +40 -0
  148. package/src/lint/post-synth/sqlpg126.ts +37 -0
  149. package/src/postgres/overlays/extension-objects.ts +100 -0
  150. package/src/postgres/overlays/types.ts +25 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Checking a ClickHouse column type against the pinned catalog, for SQLCH121
3
+ * (the family), SQLCH122 (its parameters) and SQLCH123 (a wrapper the server
4
+ * refuses). One walk over the type answers all three; each check keeps the
5
+ * findings with its own id.
6
+ *
7
+ * Families come from `system.data_type_families` (`TYPE_FAMILIES`): a name is
8
+ * case-sensitive unless the catalog marks it case-insensitive, and an alias
9
+ * resolves to its family. Parameters come from the `TYPE_PARAMETERS` overlay.
10
+ * The walk goes into every parameter that is itself a type, so `UInt46`
11
+ * inside `Array(Map(String, UInt46))` is found.
12
+ */
13
+
14
+ import { CLICKHOUSE_VERSION, TYPE_FAMILIES } from "../../generated/clickhouse";
15
+ import { TYPE_PARAMETERS, type TypeParameter } from "../../clickhouse/overlays/types";
16
+ import { numericParam, readType, typeTokens, type TypeParam } from "../../clickhouse/type-syntax";
17
+ import type { Token } from "../../clickhouse/tokens";
18
+ import { checkOf } from "./clickhouse-helpers";
19
+ import { clickhouseObjects, type OutputObject } from "./sql-helpers";
20
+
21
+ export interface TypeFinding {
22
+ checkId: "SQLCH121" | "SQLCH122" | "SQLCH123";
23
+ /** What is wrong, without the column: `UInt46 is not a type family ClickHouse 26.8.15.10 has`. */
24
+ detail: string;
25
+ }
26
+
27
+ type FamilySpec = { canonical: string; caseInsensitive: boolean };
28
+ const FAMILIES = TYPE_FAMILIES as Readonly<Record<string, FamilySpec | undefined>>;
29
+ const FOLDED = new Map<string, FamilySpec>();
30
+ const BY_LOWER = new Map<string, string>();
31
+ for (const [name, spec] of Object.entries(FAMILIES)) {
32
+ if (spec?.caseInsensitive) FOLDED.set(name.toLowerCase(), spec);
33
+ BY_LOWER.set(name.toLowerCase(), name);
34
+ }
35
+
36
+ /** A family by name as the server resolves it: exactly, or in any case when the catalog marks it case-insensitive. */
37
+ export function familyOf(name: string): FamilySpec | undefined {
38
+ return FAMILIES[name] ?? FOLDED.get(name.toLowerCase());
39
+ }
40
+
41
+ /** The MySQL-compatible words the parser keeps after a type (`INT(11) UNSIGNED`); the server ignores them. */
42
+ const MODIFIERS = new Set(["UNSIGNED", "SIGNED", "AUTO_INCREMENT", "ZEROFILL"]);
43
+
44
+ /**
45
+ * What `Nullable(...)` cannot hold. `Tuple` is not here: the pinned server
46
+ * takes `Nullable(Tuple(...))` when `enable_nullable_tuple_type` is on, a
47
+ * setting of the server's profile that the build cannot see.
48
+ */
49
+ const NOT_INSIDE_NULLABLE: Record<string, string> = {
50
+ Array: "make the elements Nullable instead, Array(Nullable(T)), or drop Nullable: an empty array stands for no value",
51
+ Map: "make the values Nullable instead, Map(K, Nullable(V)), or drop Nullable",
52
+ Nested: "make the fields Nullable instead",
53
+ LowCardinality: "write LowCardinality(Nullable(T)) instead",
54
+ Variant: "drop Nullable: a Variant already holds NULL",
55
+ Nullable: "drop one Nullable",
56
+ };
57
+
58
+ /** Families whose parameters are settings or expressions rather than types and literals; left to the server. */
59
+ const OPAQUE = new Set(["JSON", "Dynamic"]);
60
+
61
+ /** Families whose one repeated parameter needs at least one value. */
62
+ const NEEDS_ONE = new Set(["Enum", "Enum8", "Enum16", "Nested", "Variant"]);
63
+
64
+ const isName = (t: Token | undefined) => t?.kind === "ident" || t?.kind === "qident";
65
+
66
+ /** Every finding for one column type, in the order the walk meets them. */
67
+ export function typeFindings(type: string, version: string): TypeFinding[] {
68
+ const tokens = typeTokens(type);
69
+ if (!tokens) return [];
70
+ const out: TypeFinding[] = [];
71
+ walk(tokens, version, out);
72
+ return out;
73
+ }
74
+
75
+ function walk(tokens: readonly Token[], version: string, out: TypeFinding[], named = false): string | undefined {
76
+ const syntax = readType(tokens);
77
+ if (!syntax) return undefined;
78
+ let words = syntax.words;
79
+ // A named tuple or Nested element, `a String`: the first word is the element's name.
80
+ if (named && words.length >= 2 && !familyOf(words.map((w) => w.text).join(" "))) {
81
+ const at = tokens.indexOf(words[0]!);
82
+ return walk(tokens.slice(at + 1), version, out);
83
+ }
84
+ if (words.some((w) => w.kind !== "ident")) return undefined;
85
+ // `INT UNSIGNED` is a family of its own; `Int32 UNSIGNED` is Int32 with a modifier the server ignores.
86
+ let spec = familyOf(words.map((w) => w.text).join(" "));
87
+ while (!spec && words.length > 1 && MODIFIERS.has(words[words.length - 1]!.text.toUpperCase())) {
88
+ words = words.slice(0, -1);
89
+ spec = familyOf(words.map((w) => w.text).join(" "));
90
+ }
91
+ const written = words.map((w) => w.text).join(" ");
92
+ if (!spec) {
93
+ const other = BY_LOWER.get(written.toLowerCase());
94
+ const hint = other ? ` (it has ${other}, and that name is case-sensitive)` : "";
95
+ out.push({ checkId: "SQLCH121", detail: `${written} is not a type family ClickHouse ${version} has${hint}` });
96
+ return undefined;
97
+ }
98
+ const family = spec.canonical;
99
+ if (OPAQUE.has(family)) return family;
100
+ const expected: readonly TypeParameter[] = TYPE_PARAMETERS[family] ?? [];
101
+ let params = syntax.params;
102
+ const say = (detail: string) => out.push({ checkId: "SQLCH122", detail });
103
+
104
+ if (params === undefined) {
105
+ const required = expected.filter((p) => !p.optional && !p.repeated);
106
+ if (required.length > 0) say(`${written} needs its ${required.map((p) => p.name).join(" and ")}`);
107
+ else if (NEEDS_ONE.has(family)) say(`${written} needs at least one ${expected[0]!.name}`);
108
+ return family;
109
+ }
110
+ // `AggregateFunction(1, uniq, UInt64)`: a leading number is the function's state version.
111
+ if (family === "AggregateFunction" && params[0] && numericParam(params[0]) !== undefined) params = params.slice(1);
112
+
113
+ if (expected.length === 0) {
114
+ if (params.length > 0) say(`${written} takes no parameters, and ${written}(${params.map((p) => p.text).join(", ")}) gives ${params.length}`);
115
+ return family;
116
+ }
117
+ const repeated = expected[expected.length - 1]!.repeated === true;
118
+ if (!repeated && params.length > expected.length) {
119
+ say(`${written} takes at most ${expected.length} parameter${expected.length === 1 ? "" : "s"} (${expected.map((p) => p.name).join(", ")}), and is given ${params.length}`);
120
+ return family;
121
+ }
122
+ const required = expected.filter((p) => !p.optional && !p.repeated).length;
123
+ if (params.length < required) {
124
+ say(`${written} needs its ${expected.slice(params.length, required).map((p) => p.name).join(" and ")}`);
125
+ return family;
126
+ }
127
+ if (params.length === 0 && NEEDS_ONE.has(family)) {
128
+ say(`${written} needs at least one ${expected[0]!.name}`);
129
+ return family;
130
+ }
131
+
132
+ params.forEach((param, i) => {
133
+ const p = expected[Math.min(i, expected.length - 1)]!;
134
+ checkParam(written, family, p, param, version, out);
135
+ });
136
+ return family;
137
+ }
138
+
139
+ function checkParam(written: string, family: string, p: TypeParameter, param: TypeParam, version: string, out: TypeFinding[]): void {
140
+ if (p.kind === "number" && p.range) {
141
+ const value = numericParam(param);
142
+ if (value !== undefined && (value < p.range[0] || value > p.range[1])) {
143
+ out.push({ checkId: "SQLCH122", detail: `${written} ${p.name} ${param.text} is outside ${p.range[0]} to ${p.range[1]}` });
144
+ }
145
+ return;
146
+ }
147
+ if (p.kind !== "type" && p.kind !== "named-type") return;
148
+ const inner = walk(param.tokens, version, out, p.kind === "named-type" && isName(param.tokens.find((t) => t.kind !== "ws" && t.kind !== "comment")));
149
+ if (family === "Nullable" && inner !== undefined && NOT_INSIDE_NULLABLE[inner]) {
150
+ out.push({ checkId: "SQLCH123", detail: `Nullable cannot hold ${inner}; ${NOT_INSIDE_NULLABLE[inner]}` });
151
+ }
152
+ }
153
+
154
+ /** Each declared column type in the build: tables, views that list their columns, and dictionary attributes. */
155
+ export function* columnTypes(objects: readonly OutputObject[]): Generator<{ object: OutputObject; column: string; type: string }> {
156
+ for (const o of objects) {
157
+ if (!/^ClickHouse::(Table|View|MaterializedView|Dictionary)$/.test(o.type)) continue;
158
+ const columns = (o.columns ?? []) as Array<{ name: string; type?: string }>;
159
+ for (const c of columns) if (c.type) yield { object: o, column: c.name, type: c.type };
160
+ }
161
+ }
162
+
163
+ /** The body of one of the three type checks: every column type's findings with that id. */
164
+ export function reportTypeFindings(id: TypeFinding["checkId"]): Parameters<typeof checkOf>[1] {
165
+ return (ctx, report) => {
166
+ for (const { object, column, type } of columnTypes(clickhouseObjects(ctx))) {
167
+ for (const f of typeFindings(type, CLICKHOUSE_VERSION)) {
168
+ if (f.checkId !== id) continue;
169
+ report({ severity: "error", message: `${object.export} (${object.name}): column ${column} ${type}: ${f.detail}`, entity: object.export });
170
+ }
171
+ }
172
+ };
173
+ }
@@ -26,4 +26,12 @@ export const postgresPostSynthAuditEntries: Record<string, RuleMeta> = {
26
26
  SQLPG116: auditRule("SQLPG116", "merge-worthy", "guidance", "An extension the target major no longer ships", "Remove the extension, or set sql.postgresMajor to an older major.", { category: "best-practice" }),
27
27
  SQLPG117: auditRule("SQLPG117", "report-only", "guidance", "A view without security_invoker runs with its owner's privileges", "Set security_invoker = true in the view's WITH options.", { category: "security" }),
28
28
  SQLPG118: auditRule("SQLPG118", "report-only", "guidance", "A table uses INHERITS where declarative partitioning replaced it", "Use PARTITION BY and PARTITION OF.", { category: "best-practice" }),
29
+ SQLPG119: auditRule("SQLPG119", "merge-worthy", "guidance", "A column, domain or sequence type the target major does not have", "Fix the type name, declare the type, or declare the extension that provides it.", { category: "correctness" }),
30
+ SQLPG120: auditRule("SQLPG120", "merge-worthy", "guidance", "A type modifier that does not fit the type", "Drop the modifier, or bring it into the type's range.", { category: "correctness" }),
31
+ SQLPG121: auditRule("SQLPG121", "merge-worthy", "guidance", "An identity column whose type is not smallint, integer or bigint", "Declare the identity column smallint, integer or bigint.", { category: "correctness" }),
32
+ SQLPG122: auditRule("SQLPG122", "merge-worthy", "guidance", "A table declares a column twice, or an enum lists a label twice", "Remove or rename the repeated column or label.", { category: "correctness" }),
33
+ SQLPG123: auditRule("SQLPG123", "merge-worthy", "guidance", "A key, index or grant column list names a column the table does not declare", "Fix the column name, or write it as ${table.columns.name}.", { category: "correctness" }),
34
+ SQLPG124: auditRule("SQLPG124", "merge-worthy", "guidance", "A foreign key names a column the referenced table lacks, or one of another type category", "Reference a declared column, and give the referencing column a type of the same category.", { category: "correctness" }),
35
+ SQLPG125: auditRule("SQLPG125", "merge-worthy", "guidance", "An index access method or operator class the target major does not have, or not for that method", "Use a method and operator class the target major has, or declare the extension that provides them.", { category: "correctness" }),
36
+ SQLPG126: auditRule("SQLPG126", "merge-worthy", "guidance", "An expression calls a function the target major does not have", "Fix the function name, declare the function, or declare the extension that provides it.", { category: "correctness" }),
29
37
  };