typekro 0.34.0 → 0.35.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/alchemy/clickhouse-schema/executor.d.ts +25 -0
- package/dist/alchemy/clickhouse-schema/executor.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/executor.js +144 -0
- package/dist/alchemy/clickhouse-schema/executor.js.map +1 -0
- package/dist/alchemy/clickhouse-schema/index.d.ts +12 -0
- package/dist/alchemy/clickhouse-schema/index.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/index.js +11 -0
- package/dist/alchemy/clickhouse-schema/index.js.map +1 -0
- package/dist/alchemy/clickhouse-schema/resource.d.ts +54 -0
- package/dist/alchemy/clickhouse-schema/resource.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/resource.js +158 -0
- package/dist/alchemy/clickhouse-schema/resource.js.map +1 -0
- package/dist/alchemy/clickhouse-schema/runner.d.ts +154 -0
- package/dist/alchemy/clickhouse-schema/runner.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/runner.js +613 -0
- package/dist/alchemy/clickhouse-schema/runner.js.map +1 -0
- package/dist/alchemy/clickhouse-schema/sql.d.ts +109 -0
- package/dist/alchemy/clickhouse-schema/sql.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/sql.js +368 -0
- package/dist/alchemy/clickhouse-schema/sql.js.map +1 -0
- package/dist/alchemy/clickhouse-schema/types.d.ts +394 -0
- package/dist/alchemy/clickhouse-schema/types.d.ts.map +1 -0
- package/dist/alchemy/clickhouse-schema/types.js +370 -0
- package/dist/alchemy/clickhouse-schema/types.js.map +1 -0
- package/dist/alchemy/index.d.ts +1 -0
- package/dist/alchemy/index.d.ts.map +1 -1
- package/dist/alchemy/index.js +2 -0
- package/dist/alchemy/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse SQL text analysis: a small lexer plus the two things that read it.
|
|
3
|
+
*
|
|
4
|
+
* TypeKro never REWRITES a statement — an author's DDL reaches the server byte for byte.
|
|
5
|
+
* It does have to READ statements for two reasons, and both need the same thing: a scan
|
|
6
|
+
* that knows where a string literal starts and ends.
|
|
7
|
+
*
|
|
8
|
+
* 1. {@link validateOnClusterStatement} decides whether a statement is actually
|
|
9
|
+
* cluster-wide, so `execution.mode: 'onCluster'` can reject a statement that would
|
|
10
|
+
* silently land on one replica instead of rewriting it into something it wasn't.
|
|
11
|
+
* 2. {@link extractStatementSecrets} collects the literals a statement contains, so the
|
|
12
|
+
* server's own error text — which quotes the offending fragment back — can be redacted
|
|
13
|
+
* against what was actually submitted rather than against a keyword list that a
|
|
14
|
+
* positional `S3('…','AKIA…','wJalr…','CSV')` walks straight past.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately not a SQL parser. It resolves quoting and comments and nothing else; every
|
|
17
|
+
* question above is answered conservatively, so an unrecognised shape is rejected (1) or
|
|
18
|
+
* over-redacted (2) rather than waved through.
|
|
19
|
+
*/
|
|
20
|
+
/** One lexical unit. Numbers and operators collapse into `punct` — nothing here reads them. */
|
|
21
|
+
export type ClickHouseSqlToken = {
|
|
22
|
+
readonly kind: 'word';
|
|
23
|
+
readonly value: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'quoted';
|
|
26
|
+
/** The DECODED value — what the server actually receives. */
|
|
27
|
+
readonly value: string;
|
|
28
|
+
/**
|
|
29
|
+
* The SOURCE slice between the quotes, escapes intact.
|
|
30
|
+
*
|
|
31
|
+
* Kept alongside the decoded value because ClickHouse echoes a bad fragment back in
|
|
32
|
+
* whichever spelling it feels like, and `pa\'ss` and `pa''ss` are the same secret as
|
|
33
|
+
* `pa'ss`. Redaction has to blank all of them; see {@link extractStatementSecrets}.
|
|
34
|
+
*/
|
|
35
|
+
readonly raw: string;
|
|
36
|
+
readonly quote: "'" | '"' | '`';
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'punct';
|
|
39
|
+
readonly value: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The C-style SOURCE spelling of a decoded value, inverting {@link CLICKHOUSE_ESCAPES}.
|
|
43
|
+
*
|
|
44
|
+
* `decode(escapeClickHouseString(value)) === value` for every value, which is the property
|
|
45
|
+
* redaction needs: a secret's raw and decoded spellings have to be two views of one string,
|
|
46
|
+
* or one of them survives into an error message.
|
|
47
|
+
*/
|
|
48
|
+
export declare function escapeClickHouseString(value: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Split a statement into words, quoted values and single punctuation characters.
|
|
51
|
+
*
|
|
52
|
+
* Comments (`--`, `#`, `/* … *\/`) are dropped. Quoted values are returned DECODED — the
|
|
53
|
+
* doubling and backslash escapes ClickHouse accepts are resolved — because both callers
|
|
54
|
+
* compare against the value the server sees. The SOURCE slice is returned as well, because
|
|
55
|
+
* the server may echo back either spelling.
|
|
56
|
+
*/
|
|
57
|
+
export declare function tokenizeClickHouseSql(statement: string): readonly ClickHouseSqlToken[];
|
|
58
|
+
/**
|
|
59
|
+
* Whether the statement carries `ON CLUSTER <cluster>` naming exactly this cluster.
|
|
60
|
+
*
|
|
61
|
+
* Case-insensitive in the keywords, exact in the cluster name, and indifferent to how
|
|
62
|
+
* the name is spelled — bare, backtick-quoted, double-quoted or single-quoted are all the
|
|
63
|
+
* same identifier to ClickHouse. A match inside a string literal cannot be mistaken for
|
|
64
|
+
* the real clause because the lexer has already resolved literals into `quoted` tokens.
|
|
65
|
+
*/
|
|
66
|
+
export declare function statementTargetsCluster(statement: string, cluster: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The leading keyword, lowercased — `undefined` for a statement that starts with anything
|
|
69
|
+
* other than a bare word.
|
|
70
|
+
*/
|
|
71
|
+
export declare function leadingKeyword(statement: string): string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Prove that a statement applies to every server, under `execution.mode: 'onCluster'`.
|
|
74
|
+
*
|
|
75
|
+
* EXACTLY ONE proof is accepted: the statement carries `ON CLUSTER <cluster>`, naming the
|
|
76
|
+
* configured cluster, so the initiating server distributes it through Keeper's DDL queue.
|
|
77
|
+
*
|
|
78
|
+
* Nothing is INFERRED from the statement's shape. An earlier version accepted a clause-free
|
|
79
|
+
* statement whose dotted references were all on a `replicatedDatabases` allow-list, which
|
|
80
|
+
* keyed on the references rather than on the DDL TARGET: `CREATE TABLE events AS
|
|
81
|
+
* analytics.source` passed the check while creating `events` on one server only. Deciding
|
|
82
|
+
* this properly means parsing the target of every DDL form ClickHouse accepts, and a
|
|
83
|
+
* parser that is subtly wrong here silently half-applies a schema — so the safe rule is
|
|
84
|
+
* the explicit one, and the author writes the clause.
|
|
85
|
+
*
|
|
86
|
+
* The statement is never rewritten. Adding `ON CLUSTER` on the author's behalf would
|
|
87
|
+
* change the semantics of DDL TypeKro did not write, and getting that wrong on a
|
|
88
|
+
* production cluster is not recoverable.
|
|
89
|
+
*
|
|
90
|
+
* Statements that legitimately cannot carry the clause — `SET`, `USE`, a single-node
|
|
91
|
+
* `SYSTEM …`, `INSERT` — do not belong under `onCluster` at all; `fanout` runs them on
|
|
92
|
+
* every server itself, and reaching every server is then the author's business.
|
|
93
|
+
*/
|
|
94
|
+
export declare function validateOnClusterStatement(statement: string, cluster: string): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Every value in the SUBMITTED statement that must not survive into a captured message,
|
|
97
|
+
* in every spelling it could be echoed in, LONGEST FIRST.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately over-broad: EVERY single-quoted literal counts, not only the ones a
|
|
100
|
+
* keyword introduces. That is the whole point — the case keyword matching misses is the
|
|
101
|
+
* positional one, `S3('https://…', '<key id>', '<secret>', 'CSV')`, where nothing in the
|
|
102
|
+
* text says which argument is the credential. Redacting a harmless literal costs a word
|
|
103
|
+
* of an error message; leaking the other kind costs the key.
|
|
104
|
+
*
|
|
105
|
+
* Longest first so that replacing one form cannot leave a shorter form of the same secret
|
|
106
|
+
* stranded inside what is left of a longer one.
|
|
107
|
+
*/
|
|
108
|
+
export declare function extractStatementSecrets(statement: string): readonly string[];
|
|
109
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/alchemy/clickhouse-schema/sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,+FAA+F;AAC/F,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;CACjC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AA4FvD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAuEtF;AAcD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAOnF;AAKD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBjG;AAqDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAwC5E"}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse SQL text analysis: a small lexer plus the two things that read it.
|
|
3
|
+
*
|
|
4
|
+
* TypeKro never REWRITES a statement — an author's DDL reaches the server byte for byte.
|
|
5
|
+
* It does have to READ statements for two reasons, and both need the same thing: a scan
|
|
6
|
+
* that knows where a string literal starts and ends.
|
|
7
|
+
*
|
|
8
|
+
* 1. {@link validateOnClusterStatement} decides whether a statement is actually
|
|
9
|
+
* cluster-wide, so `execution.mode: 'onCluster'` can reject a statement that would
|
|
10
|
+
* silently land on one replica instead of rewriting it into something it wasn't.
|
|
11
|
+
* 2. {@link extractStatementSecrets} collects the literals a statement contains, so the
|
|
12
|
+
* server's own error text — which quotes the offending fragment back — can be redacted
|
|
13
|
+
* against what was actually submitted rather than against a keyword list that a
|
|
14
|
+
* positional `S3('…','AKIA…','wJalr…','CSV')` walks straight past.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately not a SQL parser. It resolves quoting and comments and nothing else; every
|
|
17
|
+
* question above is answered conservatively, so an unrecognised shape is rejected (1) or
|
|
18
|
+
* over-redacted (2) rather than waved through.
|
|
19
|
+
*/
|
|
20
|
+
const WORD_START = /[A-Za-z_]/;
|
|
21
|
+
const WORD_BODY = /[A-Za-z0-9_$]/;
|
|
22
|
+
/**
|
|
23
|
+
* ClickHouse's C-style escape table — the SINGLE source of truth for both directions.
|
|
24
|
+
*
|
|
25
|
+
* Reading it wrong is a redaction hole, not a cosmetic bug: a decoder that turns every
|
|
26
|
+
* `\c` into `c` decodes `\n` to the letter `n`, so a credential whose value contains a
|
|
27
|
+
* newline is extracted in a spelling the server never emits, and the real decoded form
|
|
28
|
+
* survives into the retained error text. Both directions therefore come from this one
|
|
29
|
+
* table: {@link decodeEscape} reads it, {@link escapeClickHouseString} inverts it, and a
|
|
30
|
+
* raw source spelling and its decoded value round-trip.
|
|
31
|
+
*
|
|
32
|
+
* Each entry is `[<character after the backslash>, <what the server decodes it to>]`, per
|
|
33
|
+
* https://clickhouse.com/docs/sql-reference/syntax#string. Handled outside the table
|
|
34
|
+
* because neither is a single character mapping: `\xHH` (a byte, see {@link decodeEscape})
|
|
35
|
+
* and `\N` (documented as "reserved, does nothing" — `SELECT 'a\Nb'` returns `ab`, so it
|
|
36
|
+
* decodes to the empty string).
|
|
37
|
+
*
|
|
38
|
+
* @see https://clickhouse.com/docs/sql-reference/syntax#string
|
|
39
|
+
*/
|
|
40
|
+
const CLICKHOUSE_ESCAPES = [
|
|
41
|
+
['a', '\x07'], // alert
|
|
42
|
+
['b', '\b'], // backspace
|
|
43
|
+
['e', '\x1b'], // escape character
|
|
44
|
+
['f', '\f'], // form feed
|
|
45
|
+
['n', '\n'], // line feed
|
|
46
|
+
['r', '\r'], // carriage return
|
|
47
|
+
['t', '\t'], // horizontal tab
|
|
48
|
+
['v', '\v'], // vertical tab
|
|
49
|
+
['0', '\0'], // null character
|
|
50
|
+
['\\', '\\'],
|
|
51
|
+
["'", "'"], // `''` is the other spelling, resolved by the tokenizer's doubling branch
|
|
52
|
+
['"', '"'],
|
|
53
|
+
['`', '`'],
|
|
54
|
+
['/', '/'],
|
|
55
|
+
['=', '='],
|
|
56
|
+
];
|
|
57
|
+
const ESCAPE_DECODE = new Map(CLICKHOUSE_ESCAPES);
|
|
58
|
+
/**
|
|
59
|
+
* The inverse table, restricted to the characters a single-quoted literal cannot carry
|
|
60
|
+
* verbatim: the backslash, the single quote, and the control characters the table names
|
|
61
|
+
* (every one of them is `<= 31`, which is how ClickHouse itself decides to drop the
|
|
62
|
+
* backslash). A `"`, a backtick, a `/` or an `=` needs no escape inside `'…'`, so the
|
|
63
|
+
* source spelling ClickHouse would echo for those is the character itself.
|
|
64
|
+
*/
|
|
65
|
+
const ESCAPE_ENCODE = new Map(CLICKHOUSE_ESCAPES.filter(([, decoded]) => decoded === '\\' || decoded === "'" || decoded <= '\x1f').map(([escape, decoded]) => [decoded, `\\${escape}`]));
|
|
66
|
+
const HEX_PAIR = /^[0-9A-Fa-f]{2}$/;
|
|
67
|
+
/**
|
|
68
|
+
* Decode the one backslash escape starting at `index` (which points AT the backslash).
|
|
69
|
+
*
|
|
70
|
+
* Returns the decoded text and how many SOURCE characters it consumed, so the tokenizer
|
|
71
|
+
* can keep the raw slice and the decoded value in step.
|
|
72
|
+
*
|
|
73
|
+
* The default branch is the one the previous implementation got wrong, and the
|
|
74
|
+
* documentation is explicit about it: "The backslash loses its special meaning i.e. it is
|
|
75
|
+
* interpreted literally should it precede characters other than the ones listed below."
|
|
76
|
+
* So an unlisted `\c` decodes to BOTH characters — `\z` is a backslash and a `z`, not a
|
|
77
|
+
* `z` — which is what lets `'Hello 100\%'` reach a `LIKE` pattern intact.
|
|
78
|
+
*
|
|
79
|
+
* @see https://clickhouse.com/docs/sql-reference/syntax#string
|
|
80
|
+
*/
|
|
81
|
+
function decodeEscape(statement, index) {
|
|
82
|
+
const next = statement[index + 1];
|
|
83
|
+
// A trailing backslash: nothing follows it to escape, so it is itself.
|
|
84
|
+
if (next === undefined)
|
|
85
|
+
return { text: '\\', consumed: 1 };
|
|
86
|
+
if (next === 'x') {
|
|
87
|
+
// `\xHH`: an 8-bit character. Exactly two hex digits, as the server's own parser
|
|
88
|
+
// reads. Anything else is not the escape, so the backslash stays literal.
|
|
89
|
+
const hex = statement.slice(index + 2, index + 4);
|
|
90
|
+
if (HEX_PAIR.test(hex)) {
|
|
91
|
+
return { text: String.fromCharCode(Number.parseInt(hex, 16)), consumed: 4 };
|
|
92
|
+
}
|
|
93
|
+
return { text: '\\x', consumed: 2 };
|
|
94
|
+
}
|
|
95
|
+
// `\N` is reserved and does nothing: `SELECT 'a\Nb'` returns `ab`.
|
|
96
|
+
if (next === 'N')
|
|
97
|
+
return { text: '', consumed: 2 };
|
|
98
|
+
const decoded = ESCAPE_DECODE.get(next);
|
|
99
|
+
if (decoded !== undefined)
|
|
100
|
+
return { text: decoded, consumed: 2 };
|
|
101
|
+
return { text: `\\${next}`, consumed: 2 };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The C-style SOURCE spelling of a decoded value, inverting {@link CLICKHOUSE_ESCAPES}.
|
|
105
|
+
*
|
|
106
|
+
* `decode(escapeClickHouseString(value)) === value` for every value, which is the property
|
|
107
|
+
* redaction needs: a secret's raw and decoded spellings have to be two views of one string,
|
|
108
|
+
* or one of them survives into an error message.
|
|
109
|
+
*/
|
|
110
|
+
export function escapeClickHouseString(value) {
|
|
111
|
+
let escaped = '';
|
|
112
|
+
for (const char of value) {
|
|
113
|
+
escaped += ESCAPE_ENCODE.get(char) ?? char;
|
|
114
|
+
}
|
|
115
|
+
return escaped;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Split a statement into words, quoted values and single punctuation characters.
|
|
119
|
+
*
|
|
120
|
+
* Comments (`--`, `#`, `/* … *\/`) are dropped. Quoted values are returned DECODED — the
|
|
121
|
+
* doubling and backslash escapes ClickHouse accepts are resolved — because both callers
|
|
122
|
+
* compare against the value the server sees. The SOURCE slice is returned as well, because
|
|
123
|
+
* the server may echo back either spelling.
|
|
124
|
+
*/
|
|
125
|
+
export function tokenizeClickHouseSql(statement) {
|
|
126
|
+
const tokens = [];
|
|
127
|
+
let index = 0;
|
|
128
|
+
while (index < statement.length) {
|
|
129
|
+
const char = statement[index];
|
|
130
|
+
// -- line comment / # line comment
|
|
131
|
+
if ((char === '-' && statement[index + 1] === '-') || char === '#') {
|
|
132
|
+
const newline = statement.indexOf('\n', index);
|
|
133
|
+
index = newline === -1 ? statement.length : newline + 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// /* block comment */
|
|
137
|
+
if (char === '/' && statement[index + 1] === '*') {
|
|
138
|
+
const end = statement.indexOf('*/', index + 2);
|
|
139
|
+
index = end === -1 ? statement.length : end + 2;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (char === ' ' || char === '\t' || char === '\n' || char === '\r') {
|
|
143
|
+
index += 1;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
147
|
+
const quote = char;
|
|
148
|
+
let value = '';
|
|
149
|
+
index += 1;
|
|
150
|
+
const start = index;
|
|
151
|
+
// An unterminated literal ends at the end of the statement, and its source slice with it.
|
|
152
|
+
let end = statement.length;
|
|
153
|
+
while (index < statement.length) {
|
|
154
|
+
const current = statement[index];
|
|
155
|
+
if (current === '\\') {
|
|
156
|
+
// ClickHouse accepts C-style escapes inside quoted values; the table in
|
|
157
|
+
// CLICKHOUSE_ESCAPES is what says how each one decodes.
|
|
158
|
+
const { text, consumed } = decodeEscape(statement, index);
|
|
159
|
+
value += text;
|
|
160
|
+
index += consumed;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (current === quote) {
|
|
164
|
+
if (statement[index + 1] === quote) {
|
|
165
|
+
// Doubled quote: an escaped quote, not the end of the value.
|
|
166
|
+
value += quote;
|
|
167
|
+
index += 2;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
end = index;
|
|
171
|
+
index += 1;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
value += current;
|
|
175
|
+
index += 1;
|
|
176
|
+
}
|
|
177
|
+
tokens.push({ kind: 'quoted', value, raw: statement.slice(start, end), quote });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (WORD_START.test(char)) {
|
|
181
|
+
let value = '';
|
|
182
|
+
while (index < statement.length && WORD_BODY.test(statement[index])) {
|
|
183
|
+
value += statement[index];
|
|
184
|
+
index += 1;
|
|
185
|
+
}
|
|
186
|
+
tokens.push({ kind: 'word', value });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
tokens.push({ kind: 'punct', value: char });
|
|
190
|
+
index += 1;
|
|
191
|
+
}
|
|
192
|
+
return tokens;
|
|
193
|
+
}
|
|
194
|
+
function isWord(token, keyword) {
|
|
195
|
+
return token?.kind === 'word' && token.value.toLowerCase() === keyword;
|
|
196
|
+
}
|
|
197
|
+
/** The value an identifier position carries, whatever quoting style spelled it. */
|
|
198
|
+
function identifierValue(token) {
|
|
199
|
+
if (!token)
|
|
200
|
+
return undefined;
|
|
201
|
+
if (token.kind === 'word')
|
|
202
|
+
return token.value;
|
|
203
|
+
if (token.kind === 'quoted')
|
|
204
|
+
return token.value;
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Whether the statement carries `ON CLUSTER <cluster>` naming exactly this cluster.
|
|
209
|
+
*
|
|
210
|
+
* Case-insensitive in the keywords, exact in the cluster name, and indifferent to how
|
|
211
|
+
* the name is spelled — bare, backtick-quoted, double-quoted or single-quoted are all the
|
|
212
|
+
* same identifier to ClickHouse. A match inside a string literal cannot be mistaken for
|
|
213
|
+
* the real clause because the lexer has already resolved literals into `quoted` tokens.
|
|
214
|
+
*/
|
|
215
|
+
export function statementTargetsCluster(statement, cluster) {
|
|
216
|
+
const tokens = tokenizeClickHouseSql(statement);
|
|
217
|
+
for (let index = 0; index + 2 < tokens.length; index += 1) {
|
|
218
|
+
if (!isWord(tokens[index], 'on') || !isWord(tokens[index + 1], 'cluster'))
|
|
219
|
+
continue;
|
|
220
|
+
if (identifierValue(tokens[index + 2]) === cluster)
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
/** Statement keywords that are session-scoped, and so can never be cluster-wide. */
|
|
226
|
+
const SESSION_SCOPE_KEYWORDS = new Set(['use', 'set']);
|
|
227
|
+
/**
|
|
228
|
+
* The leading keyword, lowercased — `undefined` for a statement that starts with anything
|
|
229
|
+
* other than a bare word.
|
|
230
|
+
*/
|
|
231
|
+
export function leadingKeyword(statement) {
|
|
232
|
+
const first = tokenizeClickHouseSql(statement)[0];
|
|
233
|
+
return first?.kind === 'word' ? first.value.toLowerCase() : undefined;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Prove that a statement applies to every server, under `execution.mode: 'onCluster'`.
|
|
237
|
+
*
|
|
238
|
+
* EXACTLY ONE proof is accepted: the statement carries `ON CLUSTER <cluster>`, naming the
|
|
239
|
+
* configured cluster, so the initiating server distributes it through Keeper's DDL queue.
|
|
240
|
+
*
|
|
241
|
+
* Nothing is INFERRED from the statement's shape. An earlier version accepted a clause-free
|
|
242
|
+
* statement whose dotted references were all on a `replicatedDatabases` allow-list, which
|
|
243
|
+
* keyed on the references rather than on the DDL TARGET: `CREATE TABLE events AS
|
|
244
|
+
* analytics.source` passed the check while creating `events` on one server only. Deciding
|
|
245
|
+
* this properly means parsing the target of every DDL form ClickHouse accepts, and a
|
|
246
|
+
* parser that is subtly wrong here silently half-applies a schema — so the safe rule is
|
|
247
|
+
* the explicit one, and the author writes the clause.
|
|
248
|
+
*
|
|
249
|
+
* The statement is never rewritten. Adding `ON CLUSTER` on the author's behalf would
|
|
250
|
+
* change the semantics of DDL TypeKro did not write, and getting that wrong on a
|
|
251
|
+
* production cluster is not recoverable.
|
|
252
|
+
*
|
|
253
|
+
* Statements that legitimately cannot carry the clause — `SET`, `USE`, a single-node
|
|
254
|
+
* `SYSTEM …`, `INSERT` — do not belong under `onCluster` at all; `fanout` runs them on
|
|
255
|
+
* every server itself, and reaching every server is then the author's business.
|
|
256
|
+
*/
|
|
257
|
+
export function validateOnClusterStatement(statement, cluster) {
|
|
258
|
+
if (statementTargetsCluster(statement, cluster))
|
|
259
|
+
return undefined;
|
|
260
|
+
const keyword = leadingKeyword(statement);
|
|
261
|
+
if (keyword !== undefined && SESSION_SCOPE_KEYWORDS.has(keyword)) {
|
|
262
|
+
return (`starts with '${keyword.toUpperCase()}', which is session-scoped and applies only to ` +
|
|
263
|
+
`the one server it runs on; statements that cannot carry an 'ON CLUSTER' clause ` +
|
|
264
|
+
`belong under execution.mode 'fanout'`);
|
|
265
|
+
}
|
|
266
|
+
return (`carries no 'ON CLUSTER ${cluster}' clause, which is the only thing that makes a ` +
|
|
267
|
+
`single execution reach every server; add the clause, or use execution.mode 'fanout'`);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Keywords whose following value is a credential.
|
|
271
|
+
*
|
|
272
|
+
* `IDENTIFIED BY` is handled separately: it is the two-word form of the same idea.
|
|
273
|
+
*/
|
|
274
|
+
const SECRET_KEYWORDS = new Set([
|
|
275
|
+
'password',
|
|
276
|
+
'token',
|
|
277
|
+
'access_key_id',
|
|
278
|
+
'secret_access_key',
|
|
279
|
+
'aws_access_key_id',
|
|
280
|
+
'aws_secret_access_key',
|
|
281
|
+
]);
|
|
282
|
+
/**
|
|
283
|
+
* Below this length a "secret" is not one, and blanking it out of the server's message
|
|
284
|
+
* would destroy the message without protecting anything.
|
|
285
|
+
*/
|
|
286
|
+
const MIN_SECRET_LENGTH = 3;
|
|
287
|
+
/** `'` doubled, the spelling `''` comes from. */
|
|
288
|
+
function doubleQuoteEscaped(value) {
|
|
289
|
+
return value.replaceAll("'", "''");
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Every spelling of one literal that could appear in the server's output.
|
|
293
|
+
*
|
|
294
|
+
* A credential containing a quote, a backslash or a control character has TWO source
|
|
295
|
+
* spellings (`pa\'ss` and `pa''ss`) plus the decoded value (`pa'ss`), and ClickHouse echoes
|
|
296
|
+
* back whichever one it feels like — frequently the source form, since what it is
|
|
297
|
+
* complaining about is the text it was given. Redacting only the decoded value therefore
|
|
298
|
+
* leaves the escaped form of the secret sitting in the error. All of them are returned; for
|
|
299
|
+
* the overwhelmingly common literal that contains none of those characters they collapse to
|
|
300
|
+
* a single string.
|
|
301
|
+
*
|
|
302
|
+
* The re-escaped form comes from {@link escapeClickHouseString}, which inverts the very
|
|
303
|
+
* table the decoder read, so the decoded value and its C-style spelling are guaranteed to be
|
|
304
|
+
* two views of one string rather than two independently-guessed ones.
|
|
305
|
+
*/
|
|
306
|
+
function secretForms(token) {
|
|
307
|
+
if (token === undefined || token.kind === 'punct')
|
|
308
|
+
return [];
|
|
309
|
+
if (token.kind === 'word')
|
|
310
|
+
return [token.value];
|
|
311
|
+
return [
|
|
312
|
+
token.value,
|
|
313
|
+
token.raw,
|
|
314
|
+
escapeClickHouseString(token.value),
|
|
315
|
+
doubleQuoteEscaped(token.value),
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Every value in the SUBMITTED statement that must not survive into a captured message,
|
|
320
|
+
* in every spelling it could be echoed in, LONGEST FIRST.
|
|
321
|
+
*
|
|
322
|
+
* Deliberately over-broad: EVERY single-quoted literal counts, not only the ones a
|
|
323
|
+
* keyword introduces. That is the whole point — the case keyword matching misses is the
|
|
324
|
+
* positional one, `S3('https://…', '<key id>', '<secret>', 'CSV')`, where nothing in the
|
|
325
|
+
* text says which argument is the credential. Redacting a harmless literal costs a word
|
|
326
|
+
* of an error message; leaking the other kind costs the key.
|
|
327
|
+
*
|
|
328
|
+
* Longest first so that replacing one form cannot leave a shorter form of the same secret
|
|
329
|
+
* stranded inside what is left of a longer one.
|
|
330
|
+
*/
|
|
331
|
+
export function extractStatementSecrets(statement) {
|
|
332
|
+
const tokens = tokenizeClickHouseSql(statement);
|
|
333
|
+
const secrets = new Set();
|
|
334
|
+
const add = (values) => {
|
|
335
|
+
for (const value of values) {
|
|
336
|
+
if (value.length >= MIN_SECRET_LENGTH)
|
|
337
|
+
secrets.add(value);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
341
|
+
const token = tokens[index];
|
|
342
|
+
if (token?.kind === 'quoted' && token.quote === "'") {
|
|
343
|
+
add(secretForms(token));
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (token?.kind !== 'word')
|
|
347
|
+
continue;
|
|
348
|
+
const keyword = token.value.toLowerCase();
|
|
349
|
+
let cursor = index + 1;
|
|
350
|
+
if (keyword === 'identified') {
|
|
351
|
+
if (!isWord(tokens[cursor], 'by'))
|
|
352
|
+
continue;
|
|
353
|
+
cursor += 1;
|
|
354
|
+
// `IDENTIFIED WITH sha256_password BY '…'` also lands here via the BY token.
|
|
355
|
+
}
|
|
356
|
+
else if (!SECRET_KEYWORDS.has(keyword)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
// Step over an assignment or separator between the keyword and its value.
|
|
360
|
+
while (tokens[cursor]?.kind === 'punct' &&
|
|
361
|
+
['=', ':', '(', ','].includes(tokens[cursor]?.value ?? '')) {
|
|
362
|
+
cursor += 1;
|
|
363
|
+
}
|
|
364
|
+
add(secretForms(tokens[cursor]));
|
|
365
|
+
}
|
|
366
|
+
return [...secrets].sort((left, right) => right.length - left.length || (left < right ? -1 : left > right ? 1 : 0));
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../../src/alchemy/clickhouse-schema/sql.ts"],"names":[],"mappings":"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"}
|