@hypequery/clickhouse 2.4.0 → 2.5.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/compiled/compile.d.ts +25 -0
- package/dist/core/compiled/compile.d.ts.map +1 -0
- package/dist/core/compiled/compile.js +134 -0
- package/dist/core/compiled/debug.d.ts +7 -0
- package/dist/core/compiled/debug.d.ts.map +1 -0
- package/dist/core/compiled/debug.js +25 -0
- package/dist/core/compiled/errors.d.ts +38 -0
- package/dist/core/compiled/errors.d.ts.map +1 -0
- package/dist/core/compiled/errors.js +91 -0
- package/dist/core/compiled/index.d.ts +15 -0
- package/dist/core/compiled/index.d.ts.map +1 -0
- package/dist/core/compiled/index.js +14 -0
- package/dist/core/compiled/parameters.d.ts +28 -0
- package/dist/core/compiled/parameters.d.ts.map +1 -0
- package/dist/core/compiled/parameters.js +282 -0
- package/dist/core/compiled/settings.d.ts +49 -0
- package/dist/core/compiled/settings.d.ts.map +1 -0
- package/dist/core/compiled/settings.js +65 -0
- package/dist/core/compiled/types.d.ts +115 -0
- package/dist/core/compiled/types.d.ts.map +1 -0
- package/dist/core/compiled/types.js +13 -0
- package/dist/core/features/analytics.d.ts.map +1 -1
- package/dist/core/features/analytics.js +2 -1
- package/dist/core/formatters/sql-formatter.d.ts.map +1 -1
- package/dist/core/formatters/sql-formatter.js +19 -6
- package/dist/core/query-builder.d.ts.map +1 -1
- package/dist/core/query-builder.js +2 -0
- package/dist/core/utils/predicate-builder.d.ts.map +1 -1
- package/dist/core/utils/predicate-builder.js +12 -2
- package/dist/core/utils/sql-parens.d.ts +25 -0
- package/dist/core/utils/sql-parens.d.ts.map +1 -0
- package/dist/core/utils/sql-parens.js +212 -0
- package/dist/core/utils.d.ts +2 -2
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +20 -6
- package/dist/dataset/sql-tag.d.ts +5 -0
- package/dist/dataset/sql-tag.d.ts.map +1 -1
- package/dist/dataset/sql-tag.js +7 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/package.json +4 -3
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CompiledIdentifiers, type CompiledOperation, type CompiledParameterDeclaration, type CompiledParameterValue, type CompiledQueryV1, type CompiledSensitivity, type CompiledSettings } from './types.js';
|
|
2
|
+
export interface CompileQueryInput {
|
|
3
|
+
readonly operation: CompiledOperation;
|
|
4
|
+
/** Trusted build/server output. Callers never author this. */
|
|
5
|
+
readonly sql: string;
|
|
6
|
+
readonly parameters?: readonly CompiledParameterDeclaration[];
|
|
7
|
+
/** Values supplied for declared parameter names. */
|
|
8
|
+
readonly values?: Readonly<Record<string, CompiledParameterValue>>;
|
|
9
|
+
readonly settings?: CompiledSettings;
|
|
10
|
+
readonly sensitivity?: CompiledSensitivity;
|
|
11
|
+
/** Server-generated authoritative identifier; the caller may add a correlation id. */
|
|
12
|
+
readonly identifiers: CompiledIdentifiers;
|
|
13
|
+
readonly deadline?: {
|
|
14
|
+
readonly callerAtEpochMs?: number;
|
|
15
|
+
readonly policyMaxMs?: number;
|
|
16
|
+
readonly nowEpochMs: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Assemble and validate a `CompiledQueryV1` (RFC 0010). This constructs the execution
|
|
21
|
+
* request beside the legacy positional path; it performs every fail-closed check before an
|
|
22
|
+
* adapter is ever handed the query, but performs no I/O.
|
|
23
|
+
*/
|
|
24
|
+
export declare function compileQueryV1(input: CompileQueryInput): CompiledQueryV1;
|
|
25
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/compile.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAmBpB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,4BAA4B,EAAE,CAAC;IAC9D,oDAAoD;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAC3C,sFAAsF;IACtF,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,eAAe,CAqCxE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { buildDebugForm } from './debug.js';
|
|
2
|
+
import { CompiledQueryError } from './errors.js';
|
|
3
|
+
import { assertNoValuesInSql, buildParameterBindings, validateParameterReferences, } from './parameters.js';
|
|
4
|
+
import { resolveCompiledDeadline, resolveCompiledSettings } from './settings.js';
|
|
5
|
+
import { COMPILED_QUERY_VERSION, } from './types.js';
|
|
6
|
+
const MAX_CORRELATION_ID_BYTES = 1024;
|
|
7
|
+
const MAX_QUERY_ID_BYTES = 200;
|
|
8
|
+
const MAX_SQL_BYTES = 1_048_576;
|
|
9
|
+
const MAX_PARAMETERS = 256;
|
|
10
|
+
const MAX_CLICKHOUSE_TYPE_BYTES = 256;
|
|
11
|
+
const MAX_SENSITIVITY_LABELS = 32;
|
|
12
|
+
const MAX_SENSITIVITY_LABEL_BYTES = 64;
|
|
13
|
+
// eslint-disable-next-line no-control-regex -- control chars are exactly what we reject
|
|
14
|
+
const CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F-\u009F]/;
|
|
15
|
+
const QUERY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
16
|
+
const SENSITIVITY_LABEL_PATTERN = /^[a-z][a-z0-9._-]*$/;
|
|
17
|
+
const LOGICAL_TYPES = new Set([
|
|
18
|
+
'array', 'boolean', 'bytes', 'date', 'datetime', 'decimal', 'enum', 'float',
|
|
19
|
+
'integer', 'map', 'null', 'string', 'tuple', 'uuid',
|
|
20
|
+
]);
|
|
21
|
+
const utf8 = new TextEncoder();
|
|
22
|
+
/**
|
|
23
|
+
* Assemble and validate a `CompiledQueryV1` (RFC 0010). This constructs the execution
|
|
24
|
+
* request beside the legacy positional path; it performs every fail-closed check before an
|
|
25
|
+
* adapter is ever handed the query, but performs no I/O.
|
|
26
|
+
*/
|
|
27
|
+
export function compileQueryV1(input) {
|
|
28
|
+
validateOperation(input.operation);
|
|
29
|
+
validateSql(input.sql);
|
|
30
|
+
const parameters = snapshotParameters(input.parameters ?? []);
|
|
31
|
+
const values = input.values ?? {};
|
|
32
|
+
validateParameterReferences(input.sql, parameters);
|
|
33
|
+
const bindings = buildParameterBindings(parameters, values);
|
|
34
|
+
assertNoValuesInSql(input.sql, bindings);
|
|
35
|
+
const settings = resolveCompiledSettings(input.settings ?? {});
|
|
36
|
+
const identifiers = validateIdentifiers(input.identifiers);
|
|
37
|
+
const deadlineInputs = input.deadline
|
|
38
|
+
? {
|
|
39
|
+
...input.deadline,
|
|
40
|
+
policyMaxMs: minimumDefined(input.deadline.policyMaxMs, settings.maxExecutionMs),
|
|
41
|
+
}
|
|
42
|
+
: undefined;
|
|
43
|
+
const deadline = deadlineInputs
|
|
44
|
+
? resolveCompiledDeadline(deadlineInputs)
|
|
45
|
+
: undefined;
|
|
46
|
+
const sensitivity = snapshotSensitivity(input.sensitivity);
|
|
47
|
+
const debug = buildDebugForm(input.sql, parameters, settings);
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
version: COMPILED_QUERY_VERSION,
|
|
50
|
+
operation: input.operation,
|
|
51
|
+
sql: input.sql,
|
|
52
|
+
parameters,
|
|
53
|
+
bindings,
|
|
54
|
+
settings,
|
|
55
|
+
identifiers,
|
|
56
|
+
deadline,
|
|
57
|
+
sensitivity,
|
|
58
|
+
debug,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function minimumDefined(left, right) {
|
|
62
|
+
if (left === undefined)
|
|
63
|
+
return right;
|
|
64
|
+
if (right === undefined)
|
|
65
|
+
return left;
|
|
66
|
+
return Math.min(left, right);
|
|
67
|
+
}
|
|
68
|
+
function validateOperation(operation) {
|
|
69
|
+
if (operation !== 'query' && operation !== 'command' && operation !== 'insert') {
|
|
70
|
+
throw new CompiledQueryError('input-invalid', 'The compiled operation is invalid.');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function validateSql(sql) {
|
|
74
|
+
if (typeof sql !== 'string' || sql.length === 0 || utf8.encode(sql).length > MAX_SQL_BYTES) {
|
|
75
|
+
throw new CompiledQueryError('input-invalid', 'The compiled SQL is empty or too large.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function snapshotParameters(input) {
|
|
79
|
+
if (!Array.isArray(input) || input.length > MAX_PARAMETERS) {
|
|
80
|
+
throw new CompiledQueryError('too-large', 'The compiled query has too many parameters.');
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze(input.map((declaration) => {
|
|
83
|
+
const logical = declaration?.type?.logical;
|
|
84
|
+
const clickHouseType = declaration?.type?.clickHouseType;
|
|
85
|
+
if (!LOGICAL_TYPES.has(logical)
|
|
86
|
+
|| typeof clickHouseType !== 'string'
|
|
87
|
+
|| clickHouseType.length === 0
|
|
88
|
+
|| CONTROL_CHAR_PATTERN.test(clickHouseType)
|
|
89
|
+
|| utf8.encode(clickHouseType).length > MAX_CLICKHOUSE_TYPE_BYTES
|
|
90
|
+
|| typeof declaration.optional !== 'boolean') {
|
|
91
|
+
throw new CompiledQueryError('input-invalid', 'A parameter declaration is invalid.');
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
name: declaration.name,
|
|
95
|
+
type: Object.freeze({ logical, clickHouseType }),
|
|
96
|
+
optional: declaration.optional,
|
|
97
|
+
});
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
function snapshotSensitivity(input) {
|
|
101
|
+
const tenantScoped = input?.tenantScoped ?? false;
|
|
102
|
+
const labels = input?.labels ?? [];
|
|
103
|
+
if (typeof tenantScoped !== 'boolean' || !Array.isArray(labels) || labels.length > MAX_SENSITIVITY_LABELS) {
|
|
104
|
+
throw new CompiledQueryError('input-invalid', 'Sensitivity metadata is invalid.');
|
|
105
|
+
}
|
|
106
|
+
const snapshot = labels.map((label) => {
|
|
107
|
+
if (typeof label !== 'string'
|
|
108
|
+
|| !SENSITIVITY_LABEL_PATTERN.test(label)
|
|
109
|
+
|| utf8.encode(label).length > MAX_SENSITIVITY_LABEL_BYTES) {
|
|
110
|
+
throw new CompiledQueryError('input-invalid', 'A sensitivity label is invalid.');
|
|
111
|
+
}
|
|
112
|
+
return label;
|
|
113
|
+
});
|
|
114
|
+
return Object.freeze({ tenantScoped, labels: Object.freeze(snapshot) });
|
|
115
|
+
}
|
|
116
|
+
function validateIdentifiers(identifiers) {
|
|
117
|
+
if (typeof identifiers.queryId !== 'string'
|
|
118
|
+
|| !QUERY_ID_PATTERN.test(identifiers.queryId)
|
|
119
|
+
|| utf8.encode(identifiers.queryId).length > MAX_QUERY_ID_BYTES) {
|
|
120
|
+
throw new CompiledQueryError('internal', 'A server-generated query id is required.');
|
|
121
|
+
}
|
|
122
|
+
const { correlationId } = identifiers;
|
|
123
|
+
if (correlationId !== undefined) {
|
|
124
|
+
if (CONTROL_CHAR_PATTERN.test(correlationId)) {
|
|
125
|
+
throw new CompiledQueryError('input-invalid', 'The correlation id contains control characters.');
|
|
126
|
+
}
|
|
127
|
+
if (utf8.encode(correlationId).length > MAX_CORRELATION_ID_BYTES) {
|
|
128
|
+
throw new CompiledQueryError('too-large', 'The correlation id exceeds the allowed size.');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return Object.freeze(correlationId === undefined
|
|
132
|
+
? { queryId: identifiers.queryId }
|
|
133
|
+
: { queryId: identifiers.queryId, correlationId });
|
|
134
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CompiledDebugForm, CompiledParameterDeclaration, CompiledSettings } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the redacted, non-executable debug form. It carries no parameter values, tenant
|
|
4
|
+
* values, credentials, or setting values — only names, declared types, and structure.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildDebugForm(sql: string, parameters: readonly CompiledParameterDeclaration[], settings: CompiledSettings): CompiledDebugForm;
|
|
7
|
+
//# sourceMappingURL=debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,4BAA4B,EAC5B,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAWpB;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,4BAA4B,EAAE,EACnD,QAAQ,EAAE,gBAAgB,GACzB,iBAAiB,CAkBnB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { replaceParameterPlaceholders } from './parameters.js';
|
|
2
|
+
/**
|
|
3
|
+
* Marker wrapping placeholders in the debug form. The guillemets make the rendered SQL
|
|
4
|
+
* deliberately invalid as database SQL — it cannot be pasted into a client and run — while
|
|
5
|
+
* still showing structure and declared types (RFC 0010 §Debug form).
|
|
6
|
+
*/
|
|
7
|
+
const DEBUG_OPEN = '«param ';
|
|
8
|
+
const DEBUG_CLOSE = '»';
|
|
9
|
+
/**
|
|
10
|
+
* Build the redacted, non-executable debug form. It carries no parameter values, tenant
|
|
11
|
+
* values, credentials, or setting values — only names, declared types, and structure.
|
|
12
|
+
*/
|
|
13
|
+
export function buildDebugForm(sql, parameters, settings) {
|
|
14
|
+
const redactedSql = replaceParameterPlaceholders(sql, (name, type) => `${DEBUG_OPEN}${name}: ${type}${DEBUG_CLOSE}`);
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
sql: redactedSql,
|
|
17
|
+
parameters: Object.freeze(parameters.map((p) => Object.freeze({
|
|
18
|
+
name: p.name,
|
|
19
|
+
type: p.type.clickHouseType,
|
|
20
|
+
optional: p.optional,
|
|
21
|
+
}))),
|
|
22
|
+
// Setting names only; values are policy and never appear in diagnostics.
|
|
23
|
+
settings: Object.freeze(Object.keys(settings).filter((key) => settings[key] !== undefined)),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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 declare const COMPILED_ERROR_CATEGORIES: readonly ["input-invalid", "unauthenticated", "forbidden", "tenant-required", "not-found", "too-large", "aborted", "deadline-exceeded", "unavailable", "internal"];
|
|
9
|
+
export type CompiledErrorCategory = (typeof COMPILED_ERROR_CATEGORIES)[number];
|
|
10
|
+
export declare function isCompiledErrorCategory(value: unknown): value is CompiledErrorCategory;
|
|
11
|
+
export declare function isClientFaultCategory(category: CompiledErrorCategory): boolean;
|
|
12
|
+
/** The stable, serializable error object surfaced to callers. */
|
|
13
|
+
export interface CompiledErrorEnvelope {
|
|
14
|
+
readonly category: CompiledErrorCategory;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
/** Authoritative query identifier; safe for logs. Absent only before one is assigned. */
|
|
17
|
+
readonly queryId?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CompiledQueryErrorOptions {
|
|
20
|
+
readonly queryId?: string;
|
|
21
|
+
/** Non-public cause retained for local logging; never serialized into the envelope. */
|
|
22
|
+
readonly cause?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The only execution-failure shape a runtime may surface for a compiled query. For
|
|
26
|
+
* server-fault categories the caller-facing message is fixed to a generic string so no
|
|
27
|
+
* adapter text, SQL, value, or tenant identifier can leak.
|
|
28
|
+
*/
|
|
29
|
+
export declare class CompiledQueryError extends Error {
|
|
30
|
+
readonly category: CompiledErrorCategory;
|
|
31
|
+
readonly queryId?: string;
|
|
32
|
+
readonly cause?: unknown;
|
|
33
|
+
constructor(category: CompiledErrorCategory, message: string, options?: CompiledQueryErrorOptions);
|
|
34
|
+
private static resolveMessage;
|
|
35
|
+
/** The redacted, serializable envelope. Only closed fields; no `cause`, no stack. */
|
|
36
|
+
toEnvelope(): CompiledErrorEnvelope;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/core/compiled/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,oKAW5B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAoB/E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,qBAAqB,CAKtF;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAE9E;AAED,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAGhC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,yBAA8B;IAUzC,OAAO,CAAC,MAAM,CAAC,cAAc;IAY7B,qFAAqF;IACrF,UAAU,IAAI,qBAAqB;CAKpC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
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';
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|