@hypequery/clickhouse 2.10.1 → 2.11.1
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/core/cte-scope.d.ts +91 -0
- package/dist/core/cte-scope.d.ts.map +1 -0
- package/dist/core/cte-scope.js +52 -0
- package/dist/core/dialects/clickhouse-dialect.d.ts.map +1 -1
- package/dist/core/dialects/clickhouse-dialect.js +4 -1
- package/dist/core/features/analytics.d.ts +13 -2
- package/dist/core/features/analytics.d.ts.map +1 -1
- package/dist/core/features/analytics.js +49 -3
- package/dist/core/features/joins.d.ts +5 -1
- package/dist/core/features/joins.d.ts.map +1 -1
- package/dist/core/features/joins.js +5 -1
- package/dist/core/formatters/sql-formatter.d.ts +1 -0
- package/dist/core/formatters/sql-formatter.d.ts.map +1 -1
- package/dist/core/formatters/sql-formatter.js +10 -3
- package/dist/core/query-builder.d.ts +89 -12
- package/dist/core/query-builder.d.ts.map +1 -1
- package/dist/core/query-builder.js +56 -4
- package/dist/core/query-node.d.ts.map +1 -1
- package/dist/core/query-node.js +7 -1
- package/dist/core/tests/integration/setup.d.ts +9 -0
- package/dist/core/tests/integration/setup.d.ts.map +1 -1
- package/dist/core/tests/test-utils.d.ts +23 -1
- package/dist/core/tests/test-utils.d.ts.map +1 -1
- package/dist/core/tests/test-utils.js +12 -3
- package/dist/core/types/builder-state.d.ts +68 -9
- package/dist/core/types/builder-state.d.ts.map +1 -1
- package/dist/core/types/builder-state.js +1 -0
- package/dist/core/types/select-types.d.ts +4 -2
- package/dist/core/types/select-types.d.ts.map +1 -1
- package/dist/core/utils/compound-parameter.d.ts +6 -0
- package/dist/core/utils/compound-parameter.d.ts.map +1 -0
- package/dist/core/utils/compound-parameter.js +70 -0
- package/dist/core/utils/cte-fragments.d.ts +17 -0
- package/dist/core/utils/cte-fragments.d.ts.map +1 -0
- package/dist/core/utils/cte-fragments.js +20 -0
- package/dist/core/utils/cte-scope-state.d.ts +5 -0
- package/dist/core/utils/cte-scope-state.d.ts.map +1 -0
- package/dist/core/utils/cte-scope-state.js +16 -0
- package/dist/core/utils/named-parameters.d.ts +30 -0
- package/dist/core/utils/named-parameters.d.ts.map +1 -0
- package/dist/core/utils/named-parameters.js +108 -0
- package/dist/core/utils/parameter-type.d.ts +10 -0
- package/dist/core/utils/parameter-type.d.ts.map +1 -0
- package/dist/core/utils/parameter-type.js +46 -0
- package/dist/core/utils/record-guards.d.ts +3 -0
- package/dist/core/utils/record-guards.d.ts.map +1 -0
- package/dist/core/utils/record-guards.js +7 -0
- package/dist/core/utils/sql-parens.d.ts +6 -0
- package/dist/core/utils/sql-parens.d.ts.map +1 -1
- package/dist/core/utils/sql-parens.js +6 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/types/base.d.ts +39 -0
- package/dist/types/base.d.ts.map +1 -1
- package/package.json +3 -4
- package/dist/core/compiled/compile.d.ts +0 -25
- package/dist/core/compiled/compile.d.ts.map +0 -1
- package/dist/core/compiled/compile.js +0 -134
- package/dist/core/compiled/debug.d.ts +0 -7
- package/dist/core/compiled/debug.d.ts.map +0 -1
- package/dist/core/compiled/debug.js +0 -25
- package/dist/core/compiled/errors.d.ts +0 -38
- package/dist/core/compiled/errors.d.ts.map +0 -1
- package/dist/core/compiled/errors.js +0 -91
- package/dist/core/compiled/index.d.ts +0 -15
- package/dist/core/compiled/index.d.ts.map +0 -1
- package/dist/core/compiled/index.js +0 -14
- package/dist/core/compiled/parameters.d.ts +0 -28
- package/dist/core/compiled/parameters.d.ts.map +0 -1
- package/dist/core/compiled/parameters.js +0 -282
- package/dist/core/compiled/settings.d.ts +0 -49
- package/dist/core/compiled/settings.d.ts.map +0 -1
- package/dist/core/compiled/settings.js +0 -65
- package/dist/core/compiled/types.d.ts +0 -115
- package/dist/core/compiled/types.d.ts.map +0 -1
- package/dist/core/compiled/types.js +0 -13
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public execution-failure envelope from RFC 0010 §Error envelope.
|
|
3
|
-
*
|
|
4
|
-
* The category set is closed and stable within version 1. It matches the frozen set in
|
|
5
|
-
* `@hypequery/protocol` (`events` module) so runtime failures and emitted diagnostics
|
|
6
|
-
* speak the same vocabulary. New categories require a new contract version.
|
|
7
|
-
*/
|
|
8
|
-
export const COMPILED_ERROR_CATEGORIES = [
|
|
9
|
-
'input-invalid',
|
|
10
|
-
'unauthenticated',
|
|
11
|
-
'forbidden',
|
|
12
|
-
'tenant-required',
|
|
13
|
-
'not-found',
|
|
14
|
-
'too-large',
|
|
15
|
-
'aborted',
|
|
16
|
-
'deadline-exceeded',
|
|
17
|
-
'unavailable',
|
|
18
|
-
'internal',
|
|
19
|
-
];
|
|
20
|
-
/**
|
|
21
|
-
* Categories that describe a client-supplied fault. Their messages MAY carry a safe,
|
|
22
|
-
* caller-facing explanation. Every other category is a server/dependency fault whose
|
|
23
|
-
* message MUST NOT expose adapter error text, SQL, values, or tenant identifiers.
|
|
24
|
-
*/
|
|
25
|
-
const CLIENT_FAULT_CATEGORIES = new Set([
|
|
26
|
-
'input-invalid',
|
|
27
|
-
'unauthenticated',
|
|
28
|
-
'forbidden',
|
|
29
|
-
'tenant-required',
|
|
30
|
-
'not-found',
|
|
31
|
-
'too-large',
|
|
32
|
-
'aborted',
|
|
33
|
-
'deadline-exceeded',
|
|
34
|
-
]);
|
|
35
|
-
const SAFE_MESSAGE_PATTERN = /[\r\n\t]/;
|
|
36
|
-
export function isCompiledErrorCategory(value) {
|
|
37
|
-
return (typeof value === 'string' &&
|
|
38
|
-
COMPILED_ERROR_CATEGORIES.includes(value));
|
|
39
|
-
}
|
|
40
|
-
export function isClientFaultCategory(category) {
|
|
41
|
-
return CLIENT_FAULT_CATEGORIES.has(category);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* The only execution-failure shape a runtime may surface for a compiled query. For
|
|
45
|
-
* server-fault categories the caller-facing message is fixed to a generic string so no
|
|
46
|
-
* adapter text, SQL, value, or tenant identifier can leak.
|
|
47
|
-
*/
|
|
48
|
-
export class CompiledQueryError extends Error {
|
|
49
|
-
category;
|
|
50
|
-
queryId;
|
|
51
|
-
cause;
|
|
52
|
-
constructor(category, message, options = {}) {
|
|
53
|
-
const safeMessage = CompiledQueryError.resolveMessage(category, message);
|
|
54
|
-
super(safeMessage);
|
|
55
|
-
this.name = 'CompiledQueryError';
|
|
56
|
-
this.category = category;
|
|
57
|
-
this.queryId = options.queryId;
|
|
58
|
-
this.cause = options.cause;
|
|
59
|
-
}
|
|
60
|
-
static resolveMessage(category, message) {
|
|
61
|
-
if (!isClientFaultCategory(category)) {
|
|
62
|
-
// Server-fault categories never carry a caller-authored message.
|
|
63
|
-
return DEFAULT_SERVER_FAULT_MESSAGE[category] ?? 'The request could not be completed.';
|
|
64
|
-
}
|
|
65
|
-
const trimmed = typeof message === 'string' ? message.trim() : '';
|
|
66
|
-
if (trimmed.length === 0 || SAFE_MESSAGE_PATTERN.test(trimmed)) {
|
|
67
|
-
return DEFAULT_CLIENT_FAULT_MESSAGE[category] ?? 'The request was rejected.';
|
|
68
|
-
}
|
|
69
|
-
return trimmed;
|
|
70
|
-
}
|
|
71
|
-
/** The redacted, serializable envelope. Only closed fields; no `cause`, no stack. */
|
|
72
|
-
toEnvelope() {
|
|
73
|
-
return this.queryId === undefined
|
|
74
|
-
? { category: this.category, message: this.message }
|
|
75
|
-
: { category: this.category, message: this.message, queryId: this.queryId };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const DEFAULT_CLIENT_FAULT_MESSAGE = {
|
|
79
|
-
'input-invalid': 'The request parameters were invalid.',
|
|
80
|
-
unauthenticated: 'Authentication is required.',
|
|
81
|
-
forbidden: 'Access is denied.',
|
|
82
|
-
'tenant-required': 'A tenant context is required.',
|
|
83
|
-
'not-found': 'The requested resource was not found.',
|
|
84
|
-
'too-large': 'The request or result exceeded an allowed bound.',
|
|
85
|
-
aborted: 'The request was cancelled.',
|
|
86
|
-
'deadline-exceeded': 'The request deadline was exceeded.',
|
|
87
|
-
};
|
|
88
|
-
const DEFAULT_SERVER_FAULT_MESSAGE = {
|
|
89
|
-
unavailable: 'The executor is temporarily unavailable.',
|
|
90
|
-
internal: 'The request could not be completed.',
|
|
91
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CompiledQuery v1 (RFC 0010) — the versioned execution-request contract for the
|
|
3
|
-
* ClickHouse runtime, introduced beside the legacy positional path. See `./types.ts`.
|
|
4
|
-
*
|
|
5
|
-
* CH-01 provides the shape, validation, and redacted debug/error surfaces. Wiring the real
|
|
6
|
-
* `@clickhouse/client` `{name:Type}` + `query_params` transport, capability negotiation,
|
|
7
|
-
* and end-to-end cancellation is CH-02+ and does not touch the legacy adapter signature.
|
|
8
|
-
*/
|
|
9
|
-
export { COMPILED_QUERY_VERSION, type CompiledDeadline, type CompiledDebugForm, type CompiledIdentifiers, type CompiledOperation, type CompiledParameterBindings, type CompiledParameterDeclaration, type CompiledParameterType, type CompiledParameterValue, type CompiledQueryV1, type CompiledSensitivity, type CompiledSettings, } from './types.js';
|
|
10
|
-
export { compileQueryV1, type CompileQueryInput } from './compile.js';
|
|
11
|
-
export { assertNoValuesInSql, buildParameterBindings, extractReferencedParameters, validateParameterReferences, } from './parameters.js';
|
|
12
|
-
export { COMPILED_SETTING_BOUNDS, type CompiledSettingName, type DeadlineInputs, type SettingBound, resolveCompiledDeadline, resolveCompiledSettings, } from './settings.js';
|
|
13
|
-
export { COMPILED_ERROR_CATEGORIES, CompiledQueryError, type CompiledErrorCategory, type CompiledErrorEnvelope, type CompiledQueryErrorOptions, isClientFaultCategory, isCompiledErrorCategory, } from './errors.js';
|
|
14
|
-
export { buildDebugForm } from './debug.js';
|
|
15
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EACjC,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CompiledQuery v1 (RFC 0010) — the versioned execution-request contract for the
|
|
3
|
-
* ClickHouse runtime, introduced beside the legacy positional path. See `./types.ts`.
|
|
4
|
-
*
|
|
5
|
-
* CH-01 provides the shape, validation, and redacted debug/error surfaces. Wiring the real
|
|
6
|
-
* `@clickhouse/client` `{name:Type}` + `query_params` transport, capability negotiation,
|
|
7
|
-
* and end-to-end cancellation is CH-02+ and does not touch the legacy adapter signature.
|
|
8
|
-
*/
|
|
9
|
-
export { COMPILED_QUERY_VERSION, } from './types.js';
|
|
10
|
-
export { compileQueryV1 } from './compile.js';
|
|
11
|
-
export { assertNoValuesInSql, buildParameterBindings, extractReferencedParameters, validateParameterReferences, } from './parameters.js';
|
|
12
|
-
export { COMPILED_SETTING_BOUNDS, resolveCompiledDeadline, resolveCompiledSettings, } from './settings.js';
|
|
13
|
-
export { COMPILED_ERROR_CATEGORIES, CompiledQueryError, isClientFaultCategory, isCompiledErrorCategory, } from './errors.js';
|
|
14
|
-
export { buildDebugForm } from './debug.js';
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { CompiledParameterBindings, CompiledParameterDeclaration, CompiledParameterValue } from './types.js';
|
|
2
|
-
/** Extract the set of parameter names a SQL text references via `{name:Type}`. */
|
|
3
|
-
export declare function extractReferencedParameters(sql: string): Set<string>;
|
|
4
|
-
export declare function replaceParameterPlaceholders(sql: string, replace: (name: string, type: string) => string): string;
|
|
5
|
-
/**
|
|
6
|
-
* Validate that the SQL only references declared parameters. Fails closed when a
|
|
7
|
-
* placeholder names an undeclared parameter (RFC 0010 §Parameters).
|
|
8
|
-
*/
|
|
9
|
-
export declare function validateParameterReferences(sql: string, declarations: readonly CompiledParameterDeclaration[]): void;
|
|
10
|
-
/**
|
|
11
|
-
* Resolve supplied values against declarations into the native `{name: value}` bindings
|
|
12
|
-
* an adapter binds to server parameters. Fail-closed rules (RFC 0010 §Parameters):
|
|
13
|
-
* - a supplied name that is not declared is rejected;
|
|
14
|
-
* - a required declared name with no supplied value is rejected;
|
|
15
|
-
* - an optional declared name may be absent;
|
|
16
|
-
* - every supplied value is validated (RFC 0001 for tagged values).
|
|
17
|
-
*
|
|
18
|
-
* No value is ever concatenated into SQL text: values live only in the returned bindings.
|
|
19
|
-
*/
|
|
20
|
-
export declare function buildParameterBindings(declarations: readonly CompiledParameterDeclaration[], values: Readonly<Record<string, CompiledParameterValue>>): CompiledParameterBindings;
|
|
21
|
-
/**
|
|
22
|
-
* Assert the invariant that no bound value has leaked into the SQL text. The compile path
|
|
23
|
-
* never substitutes values, so this is a defense-in-depth check: the SQL must reference
|
|
24
|
-
* every non-optional bound name through a placeholder and must not be the legacy
|
|
25
|
-
* positional form (`?`).
|
|
26
|
-
*/
|
|
27
|
-
export declare function assertNoValuesInSql(sql: string, bindings: CompiledParameterBindings): void;
|
|
28
|
-
//# sourceMappingURL=parameters.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/parameters.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,yBAAyB,EACzB,4BAA4B,EAC5B,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAsIpB,kFAAkF;AAClF,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAEpE;AAED,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAC9C,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,SAAS,4BAA4B,EAAE,GACpD,IAAI,CAoBN;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,SAAS,4BAA4B,EAAE,EACrD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,GACvD,yBAAyB,CAyC3B;AAmFD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,yBAAyB,GAClC,IAAI,CAyBN"}
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { ProtocolValueError, isProtocolIdentifier, validateCanonicalValue, } from '@hypequery/protocol';
|
|
2
|
-
import { CompiledQueryError } from './errors.js';
|
|
3
|
-
const LOGICAL_TYPES = new Set([
|
|
4
|
-
'array', 'boolean', 'bytes', 'date', 'datetime', 'decimal', 'enum', 'float',
|
|
5
|
-
'integer', 'map', 'null', 'string', 'tuple', 'uuid',
|
|
6
|
-
]);
|
|
7
|
-
function skipQuoted(sql, start, quote) {
|
|
8
|
-
let index = start + 1;
|
|
9
|
-
while (index < sql.length) {
|
|
10
|
-
if (sql[index] === '\\') {
|
|
11
|
-
index += 2;
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
if (sql[index] === quote) {
|
|
15
|
-
if (sql[index + 1] === quote) {
|
|
16
|
-
index += 2;
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
return index + 1;
|
|
20
|
-
}
|
|
21
|
-
index += 1;
|
|
22
|
-
}
|
|
23
|
-
return sql.length;
|
|
24
|
-
}
|
|
25
|
-
function parsePlaceholder(sql, start) {
|
|
26
|
-
let index = start + 1;
|
|
27
|
-
while (/\s/.test(sql[index] ?? ''))
|
|
28
|
-
index += 1;
|
|
29
|
-
const nameStart = index;
|
|
30
|
-
if (!/[A-Za-z_]/.test(sql[index] ?? ''))
|
|
31
|
-
return undefined;
|
|
32
|
-
index += 1;
|
|
33
|
-
while (/[A-Za-z0-9_]/.test(sql[index] ?? ''))
|
|
34
|
-
index += 1;
|
|
35
|
-
const name = sql.slice(nameStart, index);
|
|
36
|
-
while (/\s/.test(sql[index] ?? ''))
|
|
37
|
-
index += 1;
|
|
38
|
-
if (sql[index] !== ':')
|
|
39
|
-
return undefined;
|
|
40
|
-
index += 1;
|
|
41
|
-
const typeStart = index;
|
|
42
|
-
let quote;
|
|
43
|
-
let parenthesisDepth = 0;
|
|
44
|
-
while (index < sql.length) {
|
|
45
|
-
const character = sql[index];
|
|
46
|
-
if (quote) {
|
|
47
|
-
if (character === '\\') {
|
|
48
|
-
index += 2;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (character === quote) {
|
|
52
|
-
if (sql[index + 1] === quote) {
|
|
53
|
-
index += 2;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
quote = undefined;
|
|
57
|
-
}
|
|
58
|
-
index += 1;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (character === "'" || character === '"' || character === '`') {
|
|
62
|
-
quote = character;
|
|
63
|
-
index += 1;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (character === '(')
|
|
67
|
-
parenthesisDepth += 1;
|
|
68
|
-
if (character === ')') {
|
|
69
|
-
parenthesisDepth -= 1;
|
|
70
|
-
if (parenthesisDepth < 0)
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
if (character === '{')
|
|
74
|
-
return undefined;
|
|
75
|
-
if (character === '}' && parenthesisDepth === 0) {
|
|
76
|
-
const type = sql.slice(typeStart, index).trim();
|
|
77
|
-
if (type.length === 0)
|
|
78
|
-
return undefined;
|
|
79
|
-
return { name, type, start, end: index + 1 };
|
|
80
|
-
}
|
|
81
|
-
index += 1;
|
|
82
|
-
}
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
function scanSqlParameters(sql) {
|
|
86
|
-
const references = [];
|
|
87
|
-
let hasPositionalPlaceholder = false;
|
|
88
|
-
let index = 0;
|
|
89
|
-
while (index < sql.length) {
|
|
90
|
-
const character = sql[index];
|
|
91
|
-
if (character === "'" || character === '"' || character === '`') {
|
|
92
|
-
index = skipQuoted(sql, index, character);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (character === '-' && sql[index + 1] === '-') {
|
|
96
|
-
const newline = sql.indexOf('\n', index + 2);
|
|
97
|
-
index = newline === -1 ? sql.length : newline + 1;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (character === '#') {
|
|
101
|
-
const newline = sql.indexOf('\n', index + 1);
|
|
102
|
-
index = newline === -1 ? sql.length : newline + 1;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (character === '/' && sql[index + 1] === '*') {
|
|
106
|
-
const close = sql.indexOf('*/', index + 2);
|
|
107
|
-
index = close === -1 ? sql.length : close + 2;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (character === '?')
|
|
111
|
-
hasPositionalPlaceholder = true;
|
|
112
|
-
if (character === '{') {
|
|
113
|
-
const reference = parsePlaceholder(sql, index);
|
|
114
|
-
if (reference) {
|
|
115
|
-
references.push(reference);
|
|
116
|
-
index = reference.end;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
index += 1;
|
|
121
|
-
}
|
|
122
|
-
return { references, hasPositionalPlaceholder };
|
|
123
|
-
}
|
|
124
|
-
/** Extract the set of parameter names a SQL text references via `{name:Type}`. */
|
|
125
|
-
export function extractReferencedParameters(sql) {
|
|
126
|
-
return new Set(scanSqlParameters(sql).references.map(({ name }) => name));
|
|
127
|
-
}
|
|
128
|
-
export function replaceParameterPlaceholders(sql, replace) {
|
|
129
|
-
const references = scanSqlParameters(sql).references;
|
|
130
|
-
let result = '';
|
|
131
|
-
let offset = 0;
|
|
132
|
-
for (const reference of references) {
|
|
133
|
-
result += sql.slice(offset, reference.start);
|
|
134
|
-
result += replace(reference.name, reference.type);
|
|
135
|
-
offset = reference.end;
|
|
136
|
-
}
|
|
137
|
-
return result + sql.slice(offset);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Validate that the SQL only references declared parameters. Fails closed when a
|
|
141
|
-
* placeholder names an undeclared parameter (RFC 0010 §Parameters).
|
|
142
|
-
*/
|
|
143
|
-
export function validateParameterReferences(sql, declarations) {
|
|
144
|
-
const declared = new Map(declarations.map((declaration) => [
|
|
145
|
-
declaration.name,
|
|
146
|
-
declaration,
|
|
147
|
-
]));
|
|
148
|
-
for (const reference of scanSqlParameters(sql).references) {
|
|
149
|
-
const declaration = declared.get(reference.name);
|
|
150
|
-
if (!declaration) {
|
|
151
|
-
throw new CompiledQueryError('input-invalid', `SQL references undeclared parameter ${reference.name}.`);
|
|
152
|
-
}
|
|
153
|
-
if (reference.type !== declaration.type.clickHouseType) {
|
|
154
|
-
throw new CompiledQueryError('input-invalid', `SQL parameter ${reference.name} does not match its declared ClickHouse type.`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Resolve supplied values against declarations into the native `{name: value}` bindings
|
|
160
|
-
* an adapter binds to server parameters. Fail-closed rules (RFC 0010 §Parameters):
|
|
161
|
-
* - a supplied name that is not declared is rejected;
|
|
162
|
-
* - a required declared name with no supplied value is rejected;
|
|
163
|
-
* - an optional declared name may be absent;
|
|
164
|
-
* - every supplied value is validated (RFC 0001 for tagged values).
|
|
165
|
-
*
|
|
166
|
-
* No value is ever concatenated into SQL text: values live only in the returned bindings.
|
|
167
|
-
*/
|
|
168
|
-
export function buildParameterBindings(declarations, values) {
|
|
169
|
-
const declaredByName = new Map();
|
|
170
|
-
for (const declaration of declarations) {
|
|
171
|
-
if (!isProtocolIdentifier(declaration.name)) {
|
|
172
|
-
throw new CompiledQueryError('input-invalid', 'Parameter declaration has an invalid name.');
|
|
173
|
-
}
|
|
174
|
-
if (declaredByName.has(declaration.name)) {
|
|
175
|
-
throw new CompiledQueryError('input-invalid', `Duplicate parameter declaration ${declaration.name}.`);
|
|
176
|
-
}
|
|
177
|
-
declaredByName.set(declaration.name, declaration);
|
|
178
|
-
}
|
|
179
|
-
for (const suppliedName of Object.keys(values)) {
|
|
180
|
-
if (!declaredByName.has(suppliedName)) {
|
|
181
|
-
throw new CompiledQueryError('input-invalid', `Value supplied for undeclared parameter ${suppliedName}.`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const bindings = {};
|
|
185
|
-
for (const [name, declaration] of declaredByName) {
|
|
186
|
-
const present = Object.prototype.hasOwnProperty.call(values, name);
|
|
187
|
-
if (!present) {
|
|
188
|
-
if (declaration.optional)
|
|
189
|
-
continue;
|
|
190
|
-
throw new CompiledQueryError('input-invalid', `Required parameter ${name} is missing.`);
|
|
191
|
-
}
|
|
192
|
-
bindings[name] = validateParameterValue(name, declaration, values[name]);
|
|
193
|
-
}
|
|
194
|
-
return Object.freeze(bindings);
|
|
195
|
-
}
|
|
196
|
-
function validateParameterValue(name, declaration, value) {
|
|
197
|
-
try {
|
|
198
|
-
const validated = validateCanonicalValue(value, {
|
|
199
|
-
declaredClickHouseType: declaration.type.clickHouseType,
|
|
200
|
-
});
|
|
201
|
-
validateLogicalType(name, declaration, validated);
|
|
202
|
-
return validated;
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
if (error instanceof ProtocolValueError) {
|
|
206
|
-
throw new CompiledQueryError('input-invalid', `Parameter ${name} failed value validation (${error.code}).`, { cause: error });
|
|
207
|
-
}
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function logicalTypeOf(value) {
|
|
212
|
-
if (value === null)
|
|
213
|
-
return 'null';
|
|
214
|
-
if (typeof value === 'string')
|
|
215
|
-
return 'string';
|
|
216
|
-
if (typeof value === 'number')
|
|
217
|
-
return 'float';
|
|
218
|
-
if (typeof value === 'boolean')
|
|
219
|
-
return 'boolean';
|
|
220
|
-
return value.$hypequery.type;
|
|
221
|
-
}
|
|
222
|
-
function unwrapNullable(type) {
|
|
223
|
-
const trimmed = type.trim();
|
|
224
|
-
if (trimmed.startsWith('Nullable(') && trimmed.endsWith(')')) {
|
|
225
|
-
return { nullable: true, inner: trimmed.slice(9, -1).trim() };
|
|
226
|
-
}
|
|
227
|
-
return { nullable: false, inner: trimmed };
|
|
228
|
-
}
|
|
229
|
-
function clickHouseTypeSupportsLogical(type, logical) {
|
|
230
|
-
const { nullable, inner } = unwrapNullable(type);
|
|
231
|
-
if (logical === 'null')
|
|
232
|
-
return nullable;
|
|
233
|
-
switch (logical) {
|
|
234
|
-
case 'string': return /^(?:String|FixedString\(\d+\)|LowCardinality\(String\))$/.test(inner);
|
|
235
|
-
case 'float': return /^Float(?:32|64)$/.test(inner);
|
|
236
|
-
case 'boolean': return /^(?:Bool|Boolean)$/.test(inner);
|
|
237
|
-
case 'integer': return /^(?:U?Int)(?:8|16|32|64|128|256)$/.test(inner);
|
|
238
|
-
case 'decimal': return /^Decimal(?:(?:32|64|128|256)\(\d+\)|\(\d+\s*,\s*\d+\))$/.test(inner);
|
|
239
|
-
case 'date': return /^(?:Date|Date32)$/.test(inner);
|
|
240
|
-
case 'datetime': return /^DateTime(?:64)?(?:\(.*\))?$/.test(inner);
|
|
241
|
-
case 'uuid': return inner === 'UUID';
|
|
242
|
-
case 'bytes': return /^(?:String|FixedString\(\d+\))$/.test(inner);
|
|
243
|
-
case 'enum': return /^Enum(?:8|16)\(.*\)$/.test(inner);
|
|
244
|
-
case 'array': return /^Array\(.+\)$/.test(inner);
|
|
245
|
-
case 'tuple': return /^Tuple\(.+\)$/.test(inner);
|
|
246
|
-
case 'map': return /^Map\(.+\)$/.test(inner);
|
|
247
|
-
default: return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function validateLogicalType(name, declaration, value) {
|
|
251
|
-
const actual = logicalTypeOf(value);
|
|
252
|
-
const declared = declaration.type.logical;
|
|
253
|
-
if (!LOGICAL_TYPES.has(declared) || (actual !== declared && actual !== 'null')) {
|
|
254
|
-
throw new CompiledQueryError('input-invalid', `Parameter ${name} does not match its declared logical type.`);
|
|
255
|
-
}
|
|
256
|
-
if (!clickHouseTypeSupportsLogical(declaration.type.clickHouseType, actual)) {
|
|
257
|
-
throw new CompiledQueryError('input-invalid', `Parameter ${name} does not match its declared ClickHouse type.`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Assert the invariant that no bound value has leaked into the SQL text. The compile path
|
|
262
|
-
* never substitutes values, so this is a defense-in-depth check: the SQL must reference
|
|
263
|
-
* every non-optional bound name through a placeholder and must not be the legacy
|
|
264
|
-
* positional form (`?`).
|
|
265
|
-
*/
|
|
266
|
-
export function assertNoValuesInSql(sql, bindings) {
|
|
267
|
-
const scan = scanSqlParameters(sql);
|
|
268
|
-
if (scan.hasPositionalPlaceholder) {
|
|
269
|
-
throw new CompiledQueryError('input-invalid', 'Compiled SQL must not use positional placeholders.');
|
|
270
|
-
}
|
|
271
|
-
const referenced = new Set(scan.references.map(({ name }) => name));
|
|
272
|
-
for (const name of Object.keys(bindings)) {
|
|
273
|
-
if (!referenced.has(name)) {
|
|
274
|
-
throw new CompiledQueryError('input-invalid', `Bound parameter ${name} is not referenced by the SQL.`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const name of referenced) {
|
|
278
|
-
if (!Object.prototype.hasOwnProperty.call(bindings, name)) {
|
|
279
|
-
throw new CompiledQueryError('input-invalid', `SQL parameter ${name} does not have a bound value.`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { CompiledDeadline, CompiledSettings } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Closed, typed settings allow-list (RFC 0010 §Settings). Each entry defines an inclusive
|
|
4
|
-
* range; products may TIGHTEN a range but never loosen it. Settings originate only from
|
|
5
|
-
* trusted components — a request can never set, override, or relax them.
|
|
6
|
-
*/
|
|
7
|
-
export interface SettingBound {
|
|
8
|
-
readonly min: number;
|
|
9
|
-
readonly max: number;
|
|
10
|
-
}
|
|
11
|
-
export declare const COMPILED_SETTING_BOUNDS: Readonly<{
|
|
12
|
-
readonly maxExecutionMs: {
|
|
13
|
-
readonly min: 1;
|
|
14
|
-
readonly max: 3600000;
|
|
15
|
-
};
|
|
16
|
-
readonly maxResultRows: {
|
|
17
|
-
readonly min: 0;
|
|
18
|
-
readonly max: 1000000000000;
|
|
19
|
-
};
|
|
20
|
-
readonly maxResultBytes: {
|
|
21
|
-
readonly min: 0;
|
|
22
|
-
readonly max: 1099511627776;
|
|
23
|
-
};
|
|
24
|
-
}>;
|
|
25
|
-
export type CompiledSettingName = keyof typeof COMPILED_SETTING_BOUNDS;
|
|
26
|
-
/**
|
|
27
|
-
* Clamp trusted settings into the allow-list. A value outside the closed range is
|
|
28
|
-
* rejected fail-closed rather than silently coerced, since settings are trusted input and
|
|
29
|
-
* an out-of-range value signals a policy bug, not caller data.
|
|
30
|
-
*/
|
|
31
|
-
export declare function resolveCompiledSettings(input: CompiledSettings): CompiledSettings;
|
|
32
|
-
export interface DeadlineInputs {
|
|
33
|
-
/** Absolute caller-supplied deadline, epoch-millis. */
|
|
34
|
-
readonly callerAtEpochMs?: number;
|
|
35
|
-
/** Policy-derived maximum window from `now`, milliseconds. */
|
|
36
|
-
readonly policyMaxMs?: number;
|
|
37
|
-
/** Current time, epoch-millis. Injected so resolution stays deterministic/testable. */
|
|
38
|
-
readonly nowEpochMs: number;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Resolve the effective deadline (RFC 0010 §Deadline and cancellation precedence).
|
|
42
|
-
*
|
|
43
|
-
* The effective deadline is the EARLIER of the caller-supplied deadline and the
|
|
44
|
-
* policy-derived maximum — a caller can shorten but never extend the window. A supplied
|
|
45
|
-
* deadline at or before `now` fails immediately with `deadline-exceeded` rather than being
|
|
46
|
-
* extended or ignored.
|
|
47
|
-
*/
|
|
48
|
-
export declare function resolveCompiledDeadline(inputs: DeadlineInputs): CompiledDeadline | undefined;
|
|
49
|
-
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;EAI8B,CAAC;AAEnE,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,uBAAuB,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAejF;AAED,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAyC5F"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { CompiledQueryError } from './errors.js';
|
|
2
|
-
export const COMPILED_SETTING_BOUNDS = Object.freeze({
|
|
3
|
-
maxExecutionMs: { min: 1, max: 3_600_000 },
|
|
4
|
-
maxResultRows: { min: 0, max: 1_000_000_000_000 },
|
|
5
|
-
maxResultBytes: { min: 0, max: 1_099_511_627_776 },
|
|
6
|
-
});
|
|
7
|
-
/**
|
|
8
|
-
* Clamp trusted settings into the allow-list. A value outside the closed range is
|
|
9
|
-
* rejected fail-closed rather than silently coerced, since settings are trusted input and
|
|
10
|
-
* an out-of-range value signals a policy bug, not caller data.
|
|
11
|
-
*/
|
|
12
|
-
export function resolveCompiledSettings(input) {
|
|
13
|
-
const resolved = {};
|
|
14
|
-
for (const key of Object.keys(COMPILED_SETTING_BOUNDS)) {
|
|
15
|
-
const value = input[key];
|
|
16
|
-
if (value === undefined)
|
|
17
|
-
continue;
|
|
18
|
-
const bound = COMPILED_SETTING_BOUNDS[key];
|
|
19
|
-
if (!Number.isInteger(value) || value < bound.min || value > bound.max) {
|
|
20
|
-
throw new CompiledQueryError('input-invalid', `Setting ${key} is outside its allowed range.`);
|
|
21
|
-
}
|
|
22
|
-
resolved[key] = value;
|
|
23
|
-
}
|
|
24
|
-
return Object.freeze(resolved);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the effective deadline (RFC 0010 §Deadline and cancellation precedence).
|
|
28
|
-
*
|
|
29
|
-
* The effective deadline is the EARLIER of the caller-supplied deadline and the
|
|
30
|
-
* policy-derived maximum — a caller can shorten but never extend the window. A supplied
|
|
31
|
-
* deadline at or before `now` fails immediately with `deadline-exceeded` rather than being
|
|
32
|
-
* extended or ignored.
|
|
33
|
-
*/
|
|
34
|
-
export function resolveCompiledDeadline(inputs) {
|
|
35
|
-
const { callerAtEpochMs, policyMaxMs, nowEpochMs } = inputs;
|
|
36
|
-
if (!Number.isSafeInteger(nowEpochMs) || nowEpochMs < 0) {
|
|
37
|
-
throw new CompiledQueryError('input-invalid', 'The current time is invalid.');
|
|
38
|
-
}
|
|
39
|
-
if (callerAtEpochMs !== undefined
|
|
40
|
-
&& (!Number.isSafeInteger(callerAtEpochMs) || callerAtEpochMs < 0)) {
|
|
41
|
-
throw new CompiledQueryError('input-invalid', 'The supplied deadline is invalid.');
|
|
42
|
-
}
|
|
43
|
-
if (policyMaxMs !== undefined && (!Number.isSafeInteger(policyMaxMs) || policyMaxMs <= 0)) {
|
|
44
|
-
throw new CompiledQueryError('input-invalid', 'The policy deadline window is invalid.');
|
|
45
|
-
}
|
|
46
|
-
const policyAt = policyMaxMs === undefined ? undefined : nowEpochMs + policyMaxMs;
|
|
47
|
-
if (policyAt !== undefined && !Number.isSafeInteger(policyAt)) {
|
|
48
|
-
throw new CompiledQueryError('input-invalid', 'The policy deadline is outside the safe range.');
|
|
49
|
-
}
|
|
50
|
-
if (callerAtEpochMs !== undefined && callerAtEpochMs <= nowEpochMs) {
|
|
51
|
-
throw new CompiledQueryError('deadline-exceeded', 'The supplied deadline is at or before the current time.');
|
|
52
|
-
}
|
|
53
|
-
if (callerAtEpochMs === undefined && policyAt === undefined) {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
if (callerAtEpochMs === undefined) {
|
|
57
|
-
return Object.freeze({ atEpochMs: policyAt, source: 'policy' });
|
|
58
|
-
}
|
|
59
|
-
if (policyAt === undefined) {
|
|
60
|
-
return Object.freeze({ atEpochMs: callerAtEpochMs, source: 'caller' });
|
|
61
|
-
}
|
|
62
|
-
return Object.freeze(callerAtEpochMs <= policyAt
|
|
63
|
-
? { atEpochMs: callerAtEpochMs, source: 'caller' }
|
|
64
|
-
: { atEpochMs: policyAt, source: 'policy' });
|
|
65
|
-
}
|