@intentius/chant-lexicon-sql 0.124.12 → 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.
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/entities.d.ts +7 -1
- package/dist/clickhouse/entities.d.ts.map +1 -1
- package/dist/clickhouse/expressions.d.ts +27 -0
- package/dist/clickhouse/expressions.d.ts.map +1 -0
- package/dist/clickhouse/parser.d.ts +12 -2
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/normalize.d.ts +11 -1
- package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
- package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.d.ts +1 -0
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse.d.ts +1 -0
- package/dist/clickhouse.d.ts.map +1 -1
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/postgres-classifier-page.d.ts.map +1 -1
- package/dist/core/cursor.d.ts +41 -0
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/interpolation.d.ts +18 -1
- package/dist/core/interpolation.d.ts.map +1 -1
- package/dist/files/clickhouse.d.ts +2 -2
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/common.d.ts +16 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/postgres.d.ts +2 -2
- package/dist/files/postgres.d.ts.map +1 -1
- package/dist/import-parser.d.ts +2 -1
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/integrity.json +19 -19
- package/dist/lint/post-synth/clickhouse-helpers.d.ts +6 -19
- package/dist/lint/post-synth/clickhouse-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-helpers.d.ts +15 -55
- package/dist/lint/post-synth/postgres-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-names.d.ts +2 -2
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
- package/dist/lint/post-synth/sql-helpers.d.ts +5 -6
- package/dist/lint/post-synth/sql-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch102.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch103.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch104.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch105.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch106.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch107.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch111.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch112.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch113.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch114.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch115.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch120.d.ts.map +1 -1
- package/dist/lint/post-synth/sqlch125.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +7 -1
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/parser.d.ts +10 -2
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/rules.d.ts +1 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres.d.ts +1 -0
- package/dist/postgres.d.ts.map +1 -1
- package/dist/read/clickhouse.d.ts +66 -0
- package/dist/read/clickhouse.d.ts.map +1 -0
- package/dist/read/objects.d.ts +99 -0
- package/dist/read/objects.d.ts.map +1 -0
- package/dist/read/postgres.d.ts +83 -0
- package/dist/read/postgres.d.ts.map +1 -0
- package/dist/read.d.ts +49 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/rules/clickhouse-helpers.ts +20 -97
- package/dist/rules/postgres-helpers.ts +18 -53
- package/dist/rules/postgres-names.ts +6 -6
- package/dist/rules/sql-helpers.ts +8 -28
- package/dist/rules/sqlch102.ts +2 -3
- package/dist/rules/sqlch103.ts +2 -3
- package/dist/rules/sqlch104.ts +2 -3
- package/dist/rules/sqlch105.ts +2 -3
- package/dist/rules/sqlch106.ts +2 -3
- package/dist/rules/sqlch107.ts +2 -3
- package/dist/rules/sqlch111.ts +2 -3
- package/dist/rules/sqlch112.ts +2 -3
- package/dist/rules/sqlch113.ts +2 -3
- package/dist/rules/sqlch114.ts +2 -3
- package/dist/rules/sqlch115.ts +2 -3
- package/dist/rules/sqlch120.ts +2 -3
- package/dist/rules/sqlch125.ts +2 -3
- package/dist/serializer.d.ts +11 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/sql-files.d.ts +17 -1
- package/dist/sql-files.d.ts.map +1 -1
- package/dist/sql-types.d.ts +3 -0
- package/dist/sql-types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/apply/statements.ts +2 -0
- package/src/clickhouse/entities.ts +8 -1
- package/src/clickhouse/expressions.ts +108 -0
- package/src/clickhouse/parser.ts +113 -31
- package/src/clickhouse/plan/diff.ts +19 -0
- package/src/clickhouse/plan/normalize.ts +27 -2
- package/src/clickhouse/plan/rebuild-handoff.ts +2 -1
- package/src/clickhouse/plan/rules.ts +7 -0
- package/src/clickhouse/testdata/server-ddl-baseline.json +67 -0
- package/src/clickhouse.ts +10 -0
- package/src/codegen/classifier-page.ts +2 -0
- package/src/codegen/postgres-classifier-page.ts +2 -0
- package/src/core/cursor.ts +96 -0
- package/src/core/interpolation.ts +32 -1
- package/src/files/clickhouse.ts +7 -5
- package/src/files/common.ts +23 -0
- package/src/files/postgres.ts +12 -8
- package/src/import-parser.ts +2 -1
- package/src/lint/post-synth/clickhouse-helpers.ts +20 -97
- package/src/lint/post-synth/postgres-helpers.ts +18 -53
- package/src/lint/post-synth/postgres-names.ts +6 -6
- package/src/lint/post-synth/sql-helpers.ts +8 -28
- package/src/lint/post-synth/sqlch102.ts +2 -3
- package/src/lint/post-synth/sqlch103.ts +2 -3
- package/src/lint/post-synth/sqlch104.ts +2 -3
- package/src/lint/post-synth/sqlch105.ts +2 -3
- package/src/lint/post-synth/sqlch106.ts +2 -3
- package/src/lint/post-synth/sqlch107.ts +2 -3
- package/src/lint/post-synth/sqlch111.ts +2 -3
- package/src/lint/post-synth/sqlch112.ts +2 -3
- package/src/lint/post-synth/sqlch113.ts +2 -3
- package/src/lint/post-synth/sqlch114.ts +2 -3
- package/src/lint/post-synth/sqlch115.ts +2 -3
- package/src/lint/post-synth/sqlch120.ts +2 -3
- package/src/lint/post-synth/sqlch125.ts +2 -3
- package/src/postgres/apply/statements.ts +2 -0
- package/src/postgres/entities.ts +9 -1
- package/src/postgres/parser.ts +31 -8
- package/src/postgres/plan/diff.ts +18 -0
- package/src/postgres/plan/normalize.ts +12 -0
- package/src/postgres/plan/rules.ts +7 -0
- package/src/postgres/testdata/server-ddl-baseline.json +38 -0
- package/src/postgres.ts +12 -0
- package/src/read/clickhouse.ts +137 -0
- package/src/read/objects.ts +137 -0
- package/src/read/postgres.ts +180 -0
- package/src/read-policies.e2e.test.ts +144 -0
- package/src/read.test.ts +143 -0
- package/src/read.ts +69 -0
- package/src/serializer.ts +37 -1
- package/src/sql-files.ts +27 -6
- package/src/sql-types.ts +19 -2
- package/src/unknown-clauses.test.ts +227 -0
package/src/core/cursor.ts
CHANGED
|
@@ -33,10 +33,56 @@ export const unquoteWith = (text: string, quotes: string): string => {
|
|
|
33
33
|
return inner;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* A clause the parser does not know, kept as written (#3872): a server
|
|
38
|
+
* release can add a clause before chant's parser reads it, and the statement
|
|
39
|
+
* still goes to the server as the author wrote it. What chant reads around it
|
|
40
|
+
* is read; the clause itself is opaque.
|
|
41
|
+
*/
|
|
42
|
+
export interface UnknownClause {
|
|
43
|
+
/** Its leading words, upper case, as a message names it: `TABLESPACE_POLICY`. */
|
|
44
|
+
clause: string;
|
|
45
|
+
/** The whole clause, from its first word to where a clause the parser knows starts. */
|
|
46
|
+
span: Span;
|
|
47
|
+
/** The column (or dictionary attribute) the clause is written on, when it is one of a column's. */
|
|
48
|
+
column?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Edit distance with a swap of two neighbours as one edit, for telling a misspelt clause from a new one. */
|
|
52
|
+
function distance(a: string, b: string): number {
|
|
53
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
|
|
54
|
+
for (let i = 1; i <= a.length; i++) {
|
|
55
|
+
for (let j = 1; j <= b.length; j++) {
|
|
56
|
+
const row = d[i]!;
|
|
57
|
+
row[j] = Math.min(d[i - 1]![j]! + 1, row[j - 1]! + 1, d[i - 1]![j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
58
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) row[j] = Math.min(row[j]!, d[i - 2]![j - 2]! + 1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return d[a.length]![b.length]!;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The known clause word `word` is a misspelling of, if any: one edit from a
|
|
66
|
+
* known word of four letters or more (two neighbouring letters swapped is
|
|
67
|
+
* one), two from one of seven or more. A word
|
|
68
|
+
* that close is taken as a typo, which fails, not as a new clause, which warns.
|
|
69
|
+
*/
|
|
70
|
+
export function misspelling(word: string, known: readonly string[]): string | undefined {
|
|
71
|
+
const w = word.toUpperCase();
|
|
72
|
+
for (const k of known) {
|
|
73
|
+
if (k.length < 4 || k === w) continue;
|
|
74
|
+
const d = distance(w, k);
|
|
75
|
+
if (d === 1 || (d === 2 && k.length >= 7 && w.length >= 7)) return k;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
36
80
|
export class SqlCursor {
|
|
37
81
|
/** Indexes of the significant (non-trivia) tokens. */
|
|
38
82
|
protected readonly sig: number[];
|
|
39
83
|
protected p = 0;
|
|
84
|
+
/** The clauses read so far that the parser does not know (#3872), in order. */
|
|
85
|
+
protected readonly unknownClauses: UnknownClause[] = [];
|
|
40
86
|
|
|
41
87
|
constructor(protected readonly tokens: Token[]) {
|
|
42
88
|
this.sig = tokens.map((t, i) => (isTrivia(t) ? -1 : i)).filter((i) => i >= 0);
|
|
@@ -161,6 +207,56 @@ export class SqlCursor {
|
|
|
161
207
|
return this.span(from, refs);
|
|
162
208
|
}
|
|
163
209
|
|
|
210
|
+
/**
|
|
211
|
+
* At a clause boundary, a clause this parser does not know (#3872): a bare
|
|
212
|
+
* word that is not one of `known`, read up to the next top-level word of
|
|
213
|
+
* `known`, the statement's end, or (with `inList`, inside a column list) the
|
|
214
|
+
* element's `,` or `)`, its parentheses balanced. Recorded and returned.
|
|
215
|
+
*
|
|
216
|
+
* Undefined when the next token cannot start one: the end, punctuation, a
|
|
217
|
+
* string, a number, a quoted name or an interpolation, a word shorter than
|
|
218
|
+
* three letters or with a digit in it, or a word of `known`
|
|
219
|
+
* (a clause the parser knows, out of place). A word one letter from a known
|
|
220
|
+
* one fails here as a misspelling, and a run holding a top-level `SELECT`,
|
|
221
|
+
* `CREATE` or `INSERT` as a query or a second statement: genuinely
|
|
222
|
+
* malformed SQL stays an error.
|
|
223
|
+
*/
|
|
224
|
+
protected unknownClause(known: readonly string[], options: { inList?: boolean; column?: string } = {}): UnknownClause | undefined {
|
|
225
|
+
const t = this.peek();
|
|
226
|
+
// A clause starts with a key word: three letters or more, no digits.
|
|
227
|
+
if (t === undefined || t.kind !== "ident" || !/^[A-Za-z_]{3,}$/.test(t.text)) return undefined;
|
|
228
|
+
const word = t.text.toUpperCase();
|
|
229
|
+
if (known.includes(word)) return undefined;
|
|
230
|
+
const near = misspelling(word, known);
|
|
231
|
+
if (near !== undefined) this.fail(`expected a clause ('${t.text}' is not one chant reads, and is one letter from ${near}: is it misspelt?)`, t);
|
|
232
|
+
const from = this.idx();
|
|
233
|
+
const startP = this.p;
|
|
234
|
+
// Its name: the first word, and when that is written in upper case, the upper-case words after it (`SQL SECURITY`), not a name it takes.
|
|
235
|
+
const words = [word];
|
|
236
|
+
const upper = (u: Token | undefined) => u?.kind === "ident" && /^[A-Z_]+$/.test(u.text) && !known.includes(u.text);
|
|
237
|
+
if (upper(t)) for (let n = 1; n < 3 && upper(this.peek(n)); n++) words.push(this.peek(n)!.text);
|
|
238
|
+
const refs: number[] = [];
|
|
239
|
+
let depth = 0;
|
|
240
|
+
for (;;) {
|
|
241
|
+
const u = this.peek();
|
|
242
|
+
if (u === undefined) break;
|
|
243
|
+
if (depth === 0) {
|
|
244
|
+
if (u.kind === "punct" && (u.text === ";" || u.text === ")" || (options.inList === true && u.text === ","))) break;
|
|
245
|
+
if (this.p > startP && u.kind === "ident" && known.includes(u.text.toUpperCase())) break;
|
|
246
|
+
// A query or a second statement is not a clause: `CREATE TABLE ... AS SELECT`, `... PARALLEL WITH CREATE ...`.
|
|
247
|
+
if (kw(u, "SELECT", "CREATE", "INSERT")) this.fail(`expected a clause (${u.text.toUpperCase()} starts a query or a statement, which is not declared here)`, u);
|
|
248
|
+
}
|
|
249
|
+
depth += this.depthChange(u);
|
|
250
|
+
if (depth < 0) break;
|
|
251
|
+
if (u.kind === "ref") refs.push(u.part);
|
|
252
|
+
this.p++;
|
|
253
|
+
}
|
|
254
|
+
if (depth !== 0) this.fail(`unbalanced parentheses in the ${words.join(" ")} clause`, t);
|
|
255
|
+
const clause: UnknownClause = { clause: words.join(" "), span: this.span(from, refs), ...(options.column !== undefined ? { column: options.column } : {}) };
|
|
256
|
+
this.unknownClauses.push(clause);
|
|
257
|
+
return clause;
|
|
258
|
+
}
|
|
259
|
+
|
|
164
260
|
/** `( ... )` balanced: the span between the parentheses. */
|
|
165
261
|
protected parenthesized(): Span {
|
|
166
262
|
this.expectPunct("(");
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { AttrRef } from "@intentius/chant/attrref";
|
|
14
14
|
import { isTrivia, SqlSyntaxError, tokenize, tokenizeText, untokenize, type LexicalRules, type Token } from "./tokens";
|
|
15
|
-
import type { Span } from "./cursor";
|
|
15
|
+
import type { Span, UnknownClause } from "./cursor";
|
|
16
16
|
import type { LineageEdge } from "./references";
|
|
17
17
|
import type { SqlObject } from "./entity";
|
|
18
18
|
import { interpolationLine, spliceText, SqlTemplateError } from "./template";
|
|
@@ -124,6 +124,37 @@ export function spanText(ctx: TemplateCtx, span: Span | undefined, path?: string
|
|
|
124
124
|
return untokenize(ctx.tokens.slice(span.from, span.to), (i) => ctx.d.renderReference(ctx.values[i], i)).trim();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* A clause the parser does not know (#3872), as an entity's props carry it:
|
|
129
|
+
* kept in the DDL as written, its text here so a plan can compare it and a
|
|
130
|
+
* build can warn about it.
|
|
131
|
+
*/
|
|
132
|
+
export interface UnparsedClause {
|
|
133
|
+
/** Its leading words, upper case: `TABLESPACE_POLICY`. */
|
|
134
|
+
clause: string;
|
|
135
|
+
/** The clause as written, interpolations rendered. */
|
|
136
|
+
text: string;
|
|
137
|
+
/** The column (or dictionary attribute) it is written on, when it is one of a column's. */
|
|
138
|
+
column?: string;
|
|
139
|
+
/** The line of the statement (the props' `ddl`) it starts on, from 1. */
|
|
140
|
+
line: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** A parsed statement's unknown clauses as props carry them; undefined when there are none. */
|
|
144
|
+
export function unparsedClauses(ctx: TemplateCtx, unknown: readonly UnknownClause[] | undefined): UnparsedClause[] | undefined {
|
|
145
|
+
if (!unknown || unknown.length === 0) return undefined;
|
|
146
|
+
const render = (i: number) => ctx.d.renderReference(ctx.values[i], i);
|
|
147
|
+
return unknown.map((u) => {
|
|
148
|
+
const before = untokenize(ctx.tokens.slice(0, u.span.from), render).replace(/^\s+/, "");
|
|
149
|
+
return {
|
|
150
|
+
clause: u.clause,
|
|
151
|
+
text: spanText(ctx, u.span, "unparsed") ?? u.clause,
|
|
152
|
+
...(u.column !== undefined ? { column: u.column } : {}),
|
|
153
|
+
line: before.split("\n").length,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
/** A parse error as the tag reports it: on the template line, or in the interpolated text it came from. */
|
|
128
159
|
export function templateSyntaxError(tag: string, parts: readonly string[], err: SqlSyntaxError): SqlTemplateError {
|
|
129
160
|
const where =
|
package/src/files/clickhouse.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { isAccessStatement, parseAccess, type AccessNode } from "../clickhouse/a
|
|
|
20
20
|
import type { Token } from "../clickhouse/tokens";
|
|
21
21
|
import { CLICKHOUSE_TAG_OF } from "../clickhouse/import/generator";
|
|
22
22
|
import { describeStatement, type ImportedObject } from "../clickhouse/import/ir";
|
|
23
|
-
import { firstLine, quoteBare, splitTokens, tokensText, type ReadOptions } from "./common";
|
|
23
|
+
import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, type FileLine, type ReadOptions } from "./common";
|
|
24
24
|
import { templateSafe, templateStrings } from "./postgres";
|
|
25
25
|
|
|
26
26
|
type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
|
|
@@ -82,15 +82,16 @@ export function clickhouseTag(type: string): Tag | undefined {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/** The objects a file of ClickHouse DDL declares. Problems are collected, not thrown, so a file is reported whole. */
|
|
85
|
-
export function readClickHouse(ddl: string, options: ReadOptions, problems: string[]): ImportedObject
|
|
86
|
-
const objects: ImportedObject
|
|
85
|
+
export function readClickHouse(ddl: string, options: ReadOptions, problems: string[]): Array<ImportedObject & FileLine> {
|
|
86
|
+
const objects: Array<ImportedObject & FileLine> = [];
|
|
87
87
|
for (const stmt of splitTokens(tokenizeText(ddl, 0), isTrivia)) {
|
|
88
88
|
let sql = tokensText(stmt);
|
|
89
|
+
const line = lineAt(ddl, stmt[0]!.start);
|
|
89
90
|
if (isAccessStatement(sql)) {
|
|
90
91
|
try {
|
|
91
92
|
const read = readAccess(sql, options);
|
|
92
93
|
if (typeof read === "string") problems.push(`${read}: ${firstLine(sql)}`);
|
|
93
|
-
else objects.push(read);
|
|
94
|
+
else objects.push({ ...read, line });
|
|
94
95
|
} catch (e) {
|
|
95
96
|
problems.push(`does not parse (${e instanceof Error ? e.message : String(e)}): ${firstLine(sql)}`);
|
|
96
97
|
}
|
|
@@ -109,7 +110,8 @@ export function readClickHouse(ddl: string, options: ReadOptions, problems: stri
|
|
|
109
110
|
const at = tokens.indexOf(only);
|
|
110
111
|
sql = tokensText(tokens.slice(0, at)) + `${quoteBare(options.schema)}.` + tokensText(tokens.slice(at));
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
const unparsed = fileUnparsed(node.unknown, tokens, sql, line);
|
|
114
|
+
objects.push({ ...describeStatement(sql), line, ...(unparsed ? { unparsed } : {}) });
|
|
113
115
|
} catch (e) {
|
|
114
116
|
problems.push(`does not parse (${e instanceof Error ? e.message : String(e)}): ${firstLine(sql)}`);
|
|
115
117
|
}
|
package/src/files/common.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** What reading a file of DDL shares across dialects. */
|
|
2
2
|
|
|
3
3
|
import type { Token } from "../core/tokens";
|
|
4
|
+
import type { UnknownClause } from "../core/cursor";
|
|
4
5
|
|
|
5
6
|
export interface ReadOptions {
|
|
6
7
|
/** Names the DDL in errors: a file's path, a command. */
|
|
@@ -47,6 +48,28 @@ export function splitTokens(tokens: readonly Token[], isTrivia: (t: Token) => bo
|
|
|
47
48
|
|
|
48
49
|
export const tokensText = (tokens: readonly Token[]): string => tokens.map((t) => t.text).join("");
|
|
49
50
|
|
|
51
|
+
/** Where an object's statement starts in its file, and the clauses in it chant does not read (#3872). */
|
|
52
|
+
export interface FileLine {
|
|
53
|
+
/** The line its statement starts on, from 1. */
|
|
54
|
+
line?: number;
|
|
55
|
+
/** Each clause the parser does not know, with the file line it starts on; absent when there are none. */
|
|
56
|
+
unparsed?: Array<{ clause: string; line: number; column?: string }>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A statement's unknown clauses with their lines in the file: `line` is the statement's, `sql` its text, `tokens` its tokens. */
|
|
60
|
+
export function fileUnparsed(
|
|
61
|
+
unknown: readonly UnknownClause[] | undefined,
|
|
62
|
+
tokens: readonly Token[],
|
|
63
|
+
sql: string,
|
|
64
|
+
line: number,
|
|
65
|
+
): FileLine["unparsed"] {
|
|
66
|
+
if (!unknown || unknown.length === 0) return undefined;
|
|
67
|
+
return unknown.map((u) => ({ clause: u.clause, line: line + lineAt(sql, tokens[u.span.from]?.start ?? 0) - 1, ...(u.column !== undefined ? { column: u.column } : {}) }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The line, from 1, that offset `at` of `text` is on. */
|
|
71
|
+
export const lineAt = (text: string, at: number): number => text.slice(0, at).split("\n").length;
|
|
72
|
+
|
|
50
73
|
/** A statement's first line, for a message. */
|
|
51
74
|
export const firstLine = (s: string): string => (s.split("\n")[0] ?? "").slice(0, 100);
|
|
52
75
|
|
package/src/files/postgres.ts
CHANGED
|
@@ -26,7 +26,7 @@ import * as tags from "../postgres/entities";
|
|
|
26
26
|
import { POSTGRES_ENTITY_TYPES, type PostgresEntityType } from "../postgres/entity-types";
|
|
27
27
|
import { POSTGRES_TAG_OF } from "../postgres/import/generator";
|
|
28
28
|
import type { ImportedPgObject } from "../postgres/import/ir";
|
|
29
|
-
import { firstLine, quoteBare, splitTokens, tokensText, type ReadOptions } from "./common";
|
|
29
|
+
import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, type FileLine, type ReadOptions } from "./common";
|
|
30
30
|
|
|
31
31
|
type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
|
|
32
32
|
|
|
@@ -54,6 +54,10 @@ interface PgFileObject {
|
|
|
54
54
|
statements: Token[][];
|
|
55
55
|
/** Table constraints folded in from ALTER TABLE ... ADD, as SQL text. */
|
|
56
56
|
constraints: string[];
|
|
57
|
+
/** The line its CREATE starts on in the file. */
|
|
58
|
+
line: number;
|
|
59
|
+
/** The clauses in it the parser does not know, with their lines (#3872). */
|
|
60
|
+
unparsed: FileLine["unparsed"];
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
/** Transaction control, which declares nothing. */
|
|
@@ -69,8 +73,8 @@ function isTransactionControl(sig: readonly Token[]): boolean {
|
|
|
69
73
|
* joined as one declaration's DDL. Problems are collected, not thrown, so a
|
|
70
74
|
* file is reported whole.
|
|
71
75
|
*/
|
|
72
|
-
export function readPostgres(ddl: string, options: ReadOptions, problems: string[]): ImportedPgObject
|
|
73
|
-
const parsed: Array<{ tokens: Token[]; node: StatementNode }> = [];
|
|
76
|
+
export function readPostgres(ddl: string, options: ReadOptions, problems: string[]): Array<ImportedPgObject & FileLine> {
|
|
77
|
+
const parsed: Array<{ tokens: Token[]; node: StatementNode; line: number }> = [];
|
|
74
78
|
const alters: Array<{ table: string[]; constraint: string; sql: string }> = [];
|
|
75
79
|
for (const stmt of splitTokens(tokenizeText(ddl, 0), isTrivia)) {
|
|
76
80
|
const sig = stmt.filter((t) => !isTrivia(t));
|
|
@@ -89,7 +93,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
89
93
|
}
|
|
90
94
|
try {
|
|
91
95
|
const tokens = tokenizeText(sql, 0);
|
|
92
|
-
for (const node of parseStatements(tokens)) parsed.push({ tokens, node });
|
|
96
|
+
for (const node of parseStatements(tokens)) parsed.push({ tokens, node, line: lineAt(ddl, stmt[0]!.start) });
|
|
93
97
|
} catch (e) {
|
|
94
98
|
const what = word(sig[0], "ALTER") && word(sig[1], "TABLE") ? "an ALTER TABLE other than ADD of a table constraint or ROW LEVEL SECURITY" : `does not parse (${e instanceof Error ? e.message : String(e)})`;
|
|
95
99
|
problems.push(`${what}: ${firstLine(sql)}`);
|
|
@@ -118,7 +122,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
118
122
|
/** Objects by namespace and name: `rel` (anything in a schema), `index`, `schema`, `extension`, `role`, and `trigger`/`policy` with their table. */
|
|
119
123
|
const owners = new Map<string, PgFileObject>();
|
|
120
124
|
const followOns: Array<{ tokens: Token[]; node: StatementNode; inserts: Set<number> }> = [];
|
|
121
|
-
for (const { tokens, node } of parsed) {
|
|
125
|
+
for (const { tokens, node, line } of parsed) {
|
|
122
126
|
const inserts = new Set<number>();
|
|
123
127
|
/** Qualifies an unqualified name in the statement, when `schema` is set. */
|
|
124
128
|
const mark = (n: NameNode | undefined) => {
|
|
@@ -170,7 +174,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
170
174
|
}
|
|
171
175
|
const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant"].includes(node.statement);
|
|
172
176
|
if (ownName && "name" in node) mark(node.name);
|
|
173
|
-
const object: PgFileObject = { node, statements: [rewrite(tokens, inserts, options.schema)], constraints: [] };
|
|
177
|
+
const object: PgFileObject = { node, statements: [rewrite(tokens, inserts, options.schema)], constraints: [], line, unparsed: fileUnparsed("unknown" in node ? node.unknown : undefined, tokens, tokensText(tokens), line) };
|
|
174
178
|
objects.push(object);
|
|
175
179
|
const k = ownerKey(node, tokens, nameOf, qualify);
|
|
176
180
|
if (k) owners.set(k, object);
|
|
@@ -196,7 +200,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
196
200
|
table.constraints.push(qualifyConstraint(alter.constraint, options.schema));
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
const out: ImportedPgObject
|
|
203
|
+
const out: Array<ImportedPgObject & FileLine> = [];
|
|
200
204
|
for (const o of objects) {
|
|
201
205
|
const [create = [], ...rest] = o.statements;
|
|
202
206
|
const ddlText = [o.constraints.length > 0 ? addConstraints(create, o.constraints) : tokensText(create), ...rest.map((c) => tokensText(c))].join(";\n");
|
|
@@ -205,7 +209,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
205
209
|
problems.push(`${identity}: ${firstLine(ddlText)}`);
|
|
206
210
|
continue;
|
|
207
211
|
}
|
|
208
|
-
out.push({ ...identity, ddl: ddlText });
|
|
212
|
+
out.push({ ...identity, ddl: ddlText, line: o.line, ...(o.unparsed ? { unparsed: o.unparsed } : {}) });
|
|
209
213
|
}
|
|
210
214
|
return out;
|
|
211
215
|
}
|
package/src/import-parser.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* anything else is ClickHouse.
|
|
10
10
|
*
|
|
11
11
|
* A statement that declares nothing, or does not parse, is an error naming
|
|
12
|
-
* it (`./sql-files.ts`); none is left out.
|
|
12
|
+
* it (`./sql-files.ts`); none is left out. A clause the parser does not know
|
|
13
|
+
* (#3872) is kept in the template as written.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import type { ParserOptions, TemplateIR, TemplateParser } from "@intentius/chant/import/parser";
|
|
@@ -8,10 +8,23 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
|
-
import { TABLE_ENGINES, TYPE_FAMILIES } from "../../generated/clickhouse";
|
|
12
11
|
import type { ColumnDef, EngineDef } from "../../clickhouse/entities";
|
|
12
|
+
import { baseEngineName, splitTop } from "../../clickhouse/expressions";
|
|
13
|
+
import { readClickHouse, type ClickHouseTableInfo, type ClickHouseViewInfo } from "../../read/clickhouse";
|
|
13
14
|
import { clickhouseObjects, type OutputObject } from "./sql-helpers";
|
|
14
15
|
|
|
16
|
+
export {
|
|
17
|
+
bareName,
|
|
18
|
+
baseEngineName,
|
|
19
|
+
engineSpec,
|
|
20
|
+
isMergeTree,
|
|
21
|
+
isNullableType,
|
|
22
|
+
keyElements,
|
|
23
|
+
splitTop,
|
|
24
|
+
typeFamily,
|
|
25
|
+
unwrapType,
|
|
26
|
+
} from "../../clickhouse/expressions";
|
|
27
|
+
|
|
15
28
|
export interface TableObject extends OutputObject {
|
|
16
29
|
columns: ColumnDef[];
|
|
17
30
|
indexes: Array<{ name: string; expr: string; type: string; granularity?: string }>;
|
|
@@ -38,28 +51,14 @@ export const isMaterializedView = (o: OutputObject): o is ViewObject => o.type =
|
|
|
38
51
|
export const isAnyView = (o: OutputObject): o is ViewObject =>
|
|
39
52
|
o.type === "ClickHouse::View" || o.type === "ClickHouse::MaterializedView";
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export function viewsOf(ctx: PostSynthContext): ViewObject[] {
|
|
46
|
-
return clickhouseObjects(ctx).filter(isAnyView);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** The engine's catalog entry, when the pinned server has the engine. */
|
|
50
|
-
export function engineSpec(engine: EngineDef | undefined) {
|
|
51
|
-
return engine ? (TABLE_ENGINES as Record<string, (typeof TABLE_ENGINES)[keyof typeof TABLE_ENGINES]>)[engine.name] : undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** A MergeTree family engine, replicated or not. */
|
|
55
|
-
export function isMergeTree(engine: EngineDef | undefined): boolean {
|
|
56
|
-
return engineSpec(engine)?.mergeTree === true;
|
|
54
|
+
/** The build's tables as the public read API types them (`@intentius/chant-lexicon-sql/read`), so a project policy reads what the checks read. */
|
|
55
|
+
export function tablesOf(ctx: PostSynthContext): ClickHouseTableInfo[] {
|
|
56
|
+
return readClickHouse(ctx).tables;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/** The
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
return spec?.replicates ?? engine.name;
|
|
59
|
+
/** The build's views and materialized views, as the public read API types them. */
|
|
60
|
+
export function viewsOf(ctx: PostSynthContext): ClickHouseViewInfo[] {
|
|
61
|
+
return readClickHouse(ctx).views;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
/**
|
|
@@ -72,63 +71,6 @@ export function engineArguments(engine: EngineDef): string[] {
|
|
|
72
71
|
return args;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
/** Split on commas that are not inside parentheses, brackets or quotes. */
|
|
76
|
-
export function splitTop(text: string, separator = ","): string[] {
|
|
77
|
-
const parts: string[] = [];
|
|
78
|
-
let depth = 0;
|
|
79
|
-
let quote: string | undefined;
|
|
80
|
-
let start = 0;
|
|
81
|
-
for (let i = 0; i < text.length; i++) {
|
|
82
|
-
const c = text[i]!;
|
|
83
|
-
if (quote) {
|
|
84
|
-
if (c === "\\") i++;
|
|
85
|
-
else if (c === quote) quote = undefined;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (c === "'" || c === '"' || c === "`") quote = c;
|
|
89
|
-
else if (c === "(" || c === "[") depth++;
|
|
90
|
-
else if (c === ")" || c === "]") depth--;
|
|
91
|
-
else if (c === separator && depth === 0) {
|
|
92
|
-
parts.push(text.slice(start, i).trim());
|
|
93
|
-
start = i + 1;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const last = text.slice(start).trim();
|
|
97
|
-
if (last !== "" || parts.length > 0) parts.push(last);
|
|
98
|
-
return parts.filter((p) => p !== "");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** `(a, b)` is the list `a`, `b`; `a, b` is the same; `tuple()` and `()` are empty. */
|
|
102
|
-
export function keyElements(expr: string): string[] {
|
|
103
|
-
let text = expr.trim();
|
|
104
|
-
if (/^tuple\s*\(\s*\)$/i.test(text) || text === "()") return [];
|
|
105
|
-
if (text.startsWith("(") && closingParen(text, 0) === text.length - 1) text = text.slice(1, -1);
|
|
106
|
-
return splitTop(text).map((e) => e.replace(/\s+(ASC|DESC)$/i, "").replace(/\s+/g, " ").trim());
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function closingParen(text: string, open: number): number {
|
|
110
|
-
let depth = 0;
|
|
111
|
-
let quote: string | undefined;
|
|
112
|
-
for (let i = open; i < text.length; i++) {
|
|
113
|
-
const c = text[i]!;
|
|
114
|
-
if (quote) {
|
|
115
|
-
if (c === "\\") i++;
|
|
116
|
-
else if (c === quote) quote = undefined;
|
|
117
|
-
} else if (c === "'" || c === '"' || c === "`") quote = c;
|
|
118
|
-
else if (c === "(") depth++;
|
|
119
|
-
else if (c === ")" && --depth === 0) return i;
|
|
120
|
-
}
|
|
121
|
-
return -1;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** The column a bare name refers to: `a` or `` `a b` ``, else undefined. */
|
|
125
|
-
export function bareName(element: string): string | undefined {
|
|
126
|
-
const text = element.trim();
|
|
127
|
-
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(text)) return text;
|
|
128
|
-
const quoted = /^`((?:[^`]|``)*)`$/.exec(text) ?? /^"((?:[^"]|"")*)"$/.exec(text);
|
|
129
|
-
return quoted ? quoted[1]!.replace(/``|""/g, (m) => m[0]!) : undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
74
|
const WORDS = new Set(
|
|
133
75
|
(
|
|
134
76
|
"AS AND OR NOT IN IS NULL LIKE ILIKE BETWEEN CASE WHEN THEN ELSE END ASC DESC INTERVAL DISTINCT TRUE FALSE " +
|
|
@@ -154,25 +96,6 @@ export function mentionedNames(expr: string): string[] {
|
|
|
154
96
|
return names;
|
|
155
97
|
}
|
|
156
98
|
|
|
157
|
-
/** A type with its `Nullable(...)` and `LowCardinality(...)` wrappers removed. */
|
|
158
|
-
export function unwrapType(type: string | undefined): string {
|
|
159
|
-
let t = (type ?? "").trim();
|
|
160
|
-
for (;;) {
|
|
161
|
-
const m = /^(Nullable|LowCardinality)\s*\(([\s\S]*)\)$/i.exec(t);
|
|
162
|
-
if (!m) return t;
|
|
163
|
-
t = m[2]!.trim();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** The canonical family of a column's type (`BIGINT UNSIGNED` is `UInt64`), or its leading word when unknown. */
|
|
168
|
-
export function typeFamily(type: string | undefined): string {
|
|
169
|
-
const head = /^[A-Za-z_][A-Za-z0-9_ ]*?(?=\s*\(|$)/.exec(unwrapType(type))?.[0]?.trim() ?? "";
|
|
170
|
-
const table = TYPE_FAMILIES as Record<string, { canonical: string } | undefined>;
|
|
171
|
-
return (table[head] ?? table[head.toUpperCase()])?.canonical ?? head;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export const isNullableType = (type: string | undefined): boolean => /^Nullable\s*\(/i.test((type ?? "").trim());
|
|
175
|
-
|
|
176
99
|
export function columnByName(table: { columns: ColumnDef[] }, name: string): ColumnDef | undefined {
|
|
177
100
|
return table.columns.find((c) => c.name === name);
|
|
178
101
|
}
|
|
@@ -8,54 +8,26 @@
|
|
|
8
8
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
9
|
import { POSTGRES_MAJORS, VERSION_RANGES } from "../../generated/postgres";
|
|
10
10
|
import type { VersionRange } from "../../postgres/catalog-types";
|
|
11
|
-
import type
|
|
12
|
-
import {
|
|
11
|
+
import { readPostgres, type PostgresIndexInfo, type PostgresTableInfo, type PostgresViewInfo } from "../../read/postgres";
|
|
12
|
+
import type { PostgresIndexJson, PostgresTableJson, PostgresViewJson } from "../../read/objects";
|
|
13
|
+
import { postgresMajorOf, type OutputObject } from "./sql-helpers";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
checks: Array<{ name?: string; expr: string; notEnforced?: boolean }>;
|
|
23
|
-
foreignKeys: Array<{ name?: string; columns: string[]; refTable: string; refColumns: string[] }>;
|
|
24
|
-
exclusions: unknown[];
|
|
25
|
-
inherits: unknown[];
|
|
26
|
-
partitionOf?: unknown;
|
|
27
|
-
ofType?: unknown;
|
|
28
|
-
persistence?: string;
|
|
29
|
-
with?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface PgIndex extends OutputObject {
|
|
33
|
-
sqlName: string;
|
|
34
|
-
schema?: string;
|
|
35
|
-
table: string;
|
|
36
|
-
tableName: string;
|
|
37
|
-
unique?: boolean;
|
|
38
|
-
method?: string;
|
|
39
|
-
elements: Array<{ expr: string; column?: string }>;
|
|
40
|
-
include?: string;
|
|
41
|
-
where?: string;
|
|
42
|
-
with?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface PgView extends OutputObject {
|
|
46
|
-
sqlName: string;
|
|
47
|
-
schema?: string;
|
|
48
|
-
query: string;
|
|
49
|
-
with?: string;
|
|
50
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* The objects as the public read API types them (`../../read/postgres.ts`,
|
|
17
|
+
* `@intentius/chant-lexicon-sql/read`): the checks and a project's own
|
|
18
|
+
* `lint.policies` read the same values.
|
|
19
|
+
*/
|
|
20
|
+
export type PgTable = PostgresTableInfo;
|
|
21
|
+
export type PgIndex = PostgresIndexInfo;
|
|
22
|
+
export type PgView = PostgresViewInfo;
|
|
51
23
|
|
|
52
|
-
export const isTable = (o: OutputObject): o is
|
|
53
|
-
export const isIndex = (o: OutputObject): o is
|
|
54
|
-
export const isView = (o: OutputObject): o is
|
|
55
|
-
export const isMaterializedView = (o: OutputObject): o is
|
|
24
|
+
export const isTable = (o: OutputObject): o is OutputObject & PostgresTableJson => o.type === "Postgres::Table";
|
|
25
|
+
export const isIndex = (o: OutputObject): o is OutputObject & PostgresIndexJson => o.type === "Postgres::Index";
|
|
26
|
+
export const isView = (o: OutputObject): o is OutputObject & PostgresViewJson => o.type === "Postgres::View";
|
|
27
|
+
export const isMaterializedView = (o: OutputObject): o is OutputObject & PostgresViewJson => o.type === "Postgres::MaterializedView";
|
|
56
28
|
|
|
57
|
-
export const tablesOf = (ctx: PostSynthContext): PgTable[] =>
|
|
58
|
-
export const indexesOf = (ctx: PostSynthContext): PgIndex[] =>
|
|
29
|
+
export const tablesOf = (ctx: PostSynthContext): PgTable[] => readPostgres(ctx).tables;
|
|
30
|
+
export const indexesOf = (ctx: PostSynthContext): PgIndex[] => readPostgres(ctx).indexes;
|
|
59
31
|
|
|
60
32
|
/** The major the build targets (`postgresMajor` in the output), else the newest pinned. */
|
|
61
33
|
export const targetMajor = (ctx: PostSynthContext): number => postgresMajorOf(ctx, PINNED_MAJOR);
|
|
@@ -87,14 +59,7 @@ export function storageParams(text: string | undefined): Map<string, string> {
|
|
|
87
59
|
}
|
|
88
60
|
|
|
89
61
|
/** The leading bare-column elements of an index, in order; stops at the first expression. */
|
|
90
|
-
export
|
|
91
|
-
const out: string[] = [];
|
|
92
|
-
for (const e of index.elements) {
|
|
93
|
-
if (!e.column) break;
|
|
94
|
-
out.push(e.column);
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
62
|
+
export const leadingColumns = (index: PgIndex): string[] => index.columns;
|
|
98
63
|
|
|
99
64
|
/** A check over the build's Postgres objects, so each check file holds its rule. */
|
|
100
65
|
export function checkOf(
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
15
|
-
import { COLUMN_TYPES, FUNCTIONS, INDEX_ACCESS_METHODS, KEYWORDS, OPERATOR_CLASSES } from "../../generated/postgres";
|
|
15
|
+
import { COLUMN_TYPES, FUNCTIONS, INDEX_ACCESS_METHODS, KEYWORDS, OPERATOR_CLASSES, POSTGRES_MAJORS } from "../../generated/postgres";
|
|
16
16
|
import { EXTENSION_OBJECTS } from "../../postgres/overlays/extension-objects";
|
|
17
17
|
import { GRAMMAR_TYPE_SPELLINGS, SERIAL_TYPES, SERIAL_WIDTH_SPELLINGS, TYPE_SPELLINGS } from "../../postgres/overlays/types";
|
|
18
18
|
import { isTrivia, tokenizeText, type Token } from "../../postgres/tokens";
|
|
19
19
|
import type { ColumnTypeSpec } from "../../postgres/catalog-types";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import { dialectObjects, recordedPostgresMajor, type PostgresTableJson as PgTable } from "../../read/objects";
|
|
21
|
+
import type { OutputObject } from "./sql-helpers";
|
|
22
22
|
|
|
23
23
|
// ── Catalog lookups ────────────────────────────────────────────────────
|
|
24
24
|
|
|
@@ -120,7 +120,7 @@ export interface PgProject {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export function projectOf(ctx: PostSynthContext): PgProject {
|
|
123
|
-
return projectFromObjects(
|
|
123
|
+
return projectFromObjects(dialectObjects(ctx, "postgres"), recordedPostgresMajor(ctx, POSTGRES_MAJORS[POSTGRES_MAJORS.length - 1]!));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/** The project a set of output objects declares, at `major`: for a reader that holds the build output itself (`chant sql types`). */
|
|
@@ -386,7 +386,7 @@ function providesFunction(e: (typeof EXTENSION_OBJECTS)[string], name: string):
|
|
|
386
386
|
export function expressionsOf(o: OutputObject): Array<{ where: string; expr: string }> {
|
|
387
387
|
const out: Array<{ where: string; expr: string }> = [];
|
|
388
388
|
if (o.type === "Postgres::Table") {
|
|
389
|
-
const t = o as PgTable;
|
|
389
|
+
const t = o as unknown as PgTable;
|
|
390
390
|
for (const c of t.columns) {
|
|
391
391
|
if (c.default) out.push({ where: `column ${c.name}'s DEFAULT`, expr: c.default });
|
|
392
392
|
if (c.generated?.expr) out.push({ where: `generated column ${c.name}`, expr: c.generated.expr });
|
|
@@ -483,7 +483,7 @@ export function interpolatedTable(p: PgProject, ref: unknown, sqlName: string |
|
|
|
483
483
|
const o = p.byExport.get(ref);
|
|
484
484
|
if (!o || o.type !== "Postgres::Table") return undefined;
|
|
485
485
|
if (sqlName !== undefined && o.sqlName !== sqlName) return undefined;
|
|
486
|
-
return o as PgTable;
|
|
486
|
+
return o as unknown as PgTable;
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
/**
|