@cyanheads/mcp-ts-core 0.8.11 → 0.8.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/CLAUDE.md +2 -1
- package/README.md +1 -1
- package/changelog/0.8.x/0.8.12.md +31 -0
- package/changelog/0.8.x/0.8.13.md +27 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +6 -1
- package/dist/config/index.js.map +1 -1
- package/dist/logs/combined.log +4 -4
- package/dist/logs/error.log +4 -4
- package/dist/services/canvas/core/CanvasInstance.d.ts +16 -5
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.js +22 -4
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +5 -11
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +5 -13
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts +2 -3
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
- package/dist/services/canvas/core/DataCanvas.js +3 -8
- package/dist/services/canvas/core/DataCanvas.js.map +1 -1
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts +24 -6
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts.map +1 -1
- package/dist/services/canvas/core/IDataCanvasProvider.js +4 -4
- package/dist/services/canvas/core/canvasFactory.d.ts +7 -15
- package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
- package/dist/services/canvas/core/canvasFactory.js +7 -16
- package/dist/services/canvas/core/canvasFactory.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts +90 -60
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/core/sqlGate.js +231 -84
- package/dist/services/canvas/core/sqlGate.js.map +1 -1
- package/dist/services/canvas/index.d.ts +2 -2
- package/dist/services/canvas/index.d.ts.map +1 -1
- package/dist/services/canvas/index.js +1 -1
- package/dist/services/canvas/index.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +59 -17
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +364 -222
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts +13 -25
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.js +15 -29
- package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts +19 -26
- package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/schemaSniffer.js +30 -56
- package/dist/services/canvas/providers/duckdb/schemaSniffer.js.map +1 -1
- package/dist/services/canvas/types.d.ts +33 -6
- package/dist/services/canvas/types.d.ts.map +1 -1
- package/dist/services/canvas/types.js +1 -2
- package/dist/services/canvas/types.js.map +1 -1
- package/dist/utils/internal/requestContext.d.ts +9 -4
- package/dist/utils/internal/requestContext.d.ts.map +1 -1
- package/dist/utils/internal/requestContext.js.map +1 -1
- package/package.json +2 -2
- package/skills/api-canvas/SKILL.md +42 -8
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Read-only SQL gate for the DataCanvas primitive. Engine-agnostic
|
|
3
|
-
* pure validators
|
|
4
|
-
* metadata (statement extraction, prepared-statement type, EXPLAIN plan JSON).
|
|
3
|
+
* pure validators the provider invokes after pulling DuckDB-specific metadata.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
5
|
+
* Four layers of enforcement, each authoritative:
|
|
7
6
|
*
|
|
8
|
-
* 1. **
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 2. **
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
7
|
+
* 1. **Function deny-list (text scan).** Pre-EXPLAIN regex against the SQL
|
|
8
|
+
* (string literals stripped) for file/HTTP-reading table functions like
|
|
9
|
+
* `read_json`, `read_parquet`. These can lower into generic SEQ_SCAN
|
|
10
|
+
* operators that pass the operator allowlist; catching them by name closes
|
|
11
|
+
* that bypass.
|
|
12
|
+
* 2. **Single-statement check.** Reject anything other than exactly one
|
|
13
|
+
* statement parsed by DuckDB. Comment-hidden second statements, Unicode
|
|
14
|
+
* tricks, and multi-statement smuggling collapse here.
|
|
15
|
+
* 3. **Statement-type check.** Require `SELECT`. DDL, DML, and utility
|
|
16
|
+
* statements (PRAGMA/ATTACH/COPY/INSTALL/LOAD/SET/EXECUTE) fail to type as
|
|
17
|
+
* SELECT.
|
|
18
|
+
* 4. **Plan-walk allowlist + denied-function rescan.** Walk the
|
|
19
|
+
* `EXPLAIN (FORMAT JSON)` tree; reject any operator outside the allowlist
|
|
20
|
+
* or any string field referencing a deny-listed function.
|
|
21
21
|
*
|
|
22
|
-
* Rejection paths throw `ValidationError` with a structured `data.reason
|
|
23
|
-
* suitable for surfacing to the agent.
|
|
22
|
+
* Rejection paths throw `ValidationError` with a structured `data.reason`.
|
|
24
23
|
*
|
|
25
24
|
* @module src/services/canvas/core/sqlGate
|
|
26
25
|
*/
|
|
@@ -28,27 +27,47 @@ import { validationError } from '../../../types-global/errors.js';
|
|
|
28
27
|
/** Subset of statement types the gate permits. */
|
|
29
28
|
export const ALLOWED_STATEMENT_TYPES = new Set(['SELECT']);
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
* Reason codes set on `validationError.data.reason` by gate assertions.
|
|
31
|
+
* Consumers can import the {@link SqlGateReason} union to translate gate
|
|
32
|
+
* denials into typed contract reasons without duplicating the strings.
|
|
33
|
+
*/
|
|
34
|
+
export const SQL_GATE_REASONS = {
|
|
35
|
+
multiStatement: 'multi_statement',
|
|
36
|
+
nonSelectStatement: 'non_select_statement',
|
|
37
|
+
planOperatorNotAllowed: 'plan_operator_not_allowed',
|
|
38
|
+
deniedFunction: 'denied_function',
|
|
39
|
+
deniedFunctionInPlan: 'denied_function_in_plan',
|
|
40
|
+
identifierEmpty: 'identifier_empty',
|
|
41
|
+
identifierShape: 'identifier_shape',
|
|
42
|
+
identifierReserved: 'identifier_reserved',
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Allowlist of read-only operator names that can appear in an EXPLAIN plan.
|
|
46
|
+
* Anything outside this set causes rejection. Notable exclusions:
|
|
40
47
|
*
|
|
41
48
|
* - `READ_CSV`, `READ_PARQUET`, `READ_JSON` — bypass canvas, read external files.
|
|
42
|
-
* - `INSERT`, `UPDATE`, `DELETE`, `
|
|
43
|
-
* - `COPY_TO_FILE`, `BATCH_COPY_TO_FILE` —
|
|
44
|
-
* - `ATTACH`, `DETACH`, `LOAD`, `
|
|
49
|
+
* - `INSERT`, `UPDATE`, `DELETE`, `MERGE_INTO`, `CREATE_*`, `DROP`, `ALTER` — writes.
|
|
50
|
+
* - `COPY_TO_FILE`, `BATCH_COPY_TO_FILE`, `COPY_DATABASE` — write a SELECT to a file/db.
|
|
51
|
+
* - `ATTACH`, `DETACH`, `LOAD`, `PRAGMA`, `SET`, `SET_VARIABLE`, `RESET`, `TRANSACTION`,
|
|
52
|
+
* `EXECUTE`, `PREPARE`, `VACUUM`, `EXPORT`, `EXPLAIN_ANALYZE`, `CREATE_SECRET` — utility/system.
|
|
53
|
+
* - `INOUT_FUNCTION` — table-valued function lowering (read_json/read_parquet/...);
|
|
54
|
+
* gated by the function deny-list rather than the operator allowlist.
|
|
55
|
+
*
|
|
56
|
+
* Source pinned against `PhysicalOperatorToString` in DuckDB v1.5.2:
|
|
57
|
+
* https://github.com/duckdb/duckdb/blob/v1.5.2/src/common/enums/physical_operator_type.cpp
|
|
45
58
|
*/
|
|
46
59
|
export const ALLOWED_PLAN_OPERATORS = new Set([
|
|
47
|
-
// Scans (registered tables only
|
|
60
|
+
// Scans (registered tables only)
|
|
61
|
+
'TABLE_SCAN',
|
|
48
62
|
'SEQ_SCAN',
|
|
49
63
|
'COLUMN_DATA_SCAN',
|
|
50
64
|
'CHUNK_SCAN',
|
|
65
|
+
'CTE_SCAN',
|
|
66
|
+
'REC_CTE_SCAN',
|
|
67
|
+
'REC_REC_CTE_SCAN',
|
|
68
|
+
'DELIM_SCAN',
|
|
51
69
|
'EXPRESSION_SCAN',
|
|
70
|
+
'POSITIONAL_SCAN',
|
|
52
71
|
'DUMMY_SCAN',
|
|
53
72
|
'EMPTY_RESULT',
|
|
54
73
|
'IN_MEMORY_TABLE_SCAN',
|
|
@@ -64,6 +83,8 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
|
|
|
64
83
|
'CROSS_PRODUCT',
|
|
65
84
|
'POSITIONAL_JOIN',
|
|
66
85
|
'ASOF_JOIN',
|
|
86
|
+
'LEFT_DELIM_JOIN',
|
|
87
|
+
'RIGHT_DELIM_JOIN',
|
|
67
88
|
'DELIM_JOIN',
|
|
68
89
|
// Aggregates
|
|
69
90
|
'HASH_GROUP_BY',
|
|
@@ -74,6 +95,7 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
|
|
|
74
95
|
// Distinct / set ops
|
|
75
96
|
'HASH_DISTINCT',
|
|
76
97
|
'DISTINCT',
|
|
98
|
+
'LIMITED_DISTINCT',
|
|
77
99
|
'UNION',
|
|
78
100
|
// Sorting / limits
|
|
79
101
|
'ORDER_BY',
|
|
@@ -83,11 +105,14 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
|
|
|
83
105
|
'STREAMING_LIMIT',
|
|
84
106
|
// Window
|
|
85
107
|
'WINDOW',
|
|
108
|
+
'STREAMING_WINDOW',
|
|
86
109
|
// Nested
|
|
87
110
|
'UNNEST',
|
|
88
|
-
// CTEs
|
|
111
|
+
// CTEs (DuckDB stringifies RECURSIVE_* as REC_*; long names kept for older versions)
|
|
89
112
|
'CTE',
|
|
90
113
|
'CTE_REF',
|
|
114
|
+
'REC_CTE',
|
|
115
|
+
'REC_KEY_CTE',
|
|
91
116
|
'RECURSIVE_CTE',
|
|
92
117
|
'MATERIALIZED_CTE',
|
|
93
118
|
// Sampling
|
|
@@ -99,68 +124,176 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
|
|
|
99
124
|
'EXPLAIN',
|
|
100
125
|
// Pivot/unpivot collapsed planner forms
|
|
101
126
|
'PIVOT',
|
|
127
|
+
// Spatial — pre-staged, dormant until the `spatial` extension loads.
|
|
128
|
+
// See https://github.com/cyanheads/mcp-ts-core/issues/106.
|
|
129
|
+
'RTREE_INDEX_SCAN',
|
|
102
130
|
]);
|
|
103
131
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
132
|
+
* External-data table functions (files, HTTP, S3, lakehouse formats). These
|
|
133
|
+
* lower into generic scan operators that pass the operator allowlist, so the
|
|
134
|
+
* text-scan and plan-rescan defenses in this module catch them by name
|
|
135
|
+
* regardless of how DuckDB lowers them.
|
|
136
|
+
*/
|
|
137
|
+
export const DENIED_TABLE_FUNCTIONS = new Set([
|
|
138
|
+
// CSV
|
|
139
|
+
'read_csv',
|
|
140
|
+
'read_csv_auto',
|
|
141
|
+
'sniff_csv',
|
|
142
|
+
// JSON
|
|
143
|
+
'read_json',
|
|
144
|
+
'read_json_auto',
|
|
145
|
+
'read_json_objects',
|
|
146
|
+
'read_json_objects_auto',
|
|
147
|
+
'read_ndjson',
|
|
148
|
+
'read_ndjson_auto',
|
|
149
|
+
'read_ndjson_objects',
|
|
150
|
+
// Parquet
|
|
151
|
+
'read_parquet',
|
|
152
|
+
'parquet_scan',
|
|
153
|
+
'parquet_metadata',
|
|
154
|
+
'parquet_schema',
|
|
155
|
+
'parquet_file_metadata',
|
|
156
|
+
'parquet_kv_metadata',
|
|
157
|
+
// Text / blob / glob
|
|
158
|
+
'read_text',
|
|
159
|
+
'read_blob',
|
|
160
|
+
'glob',
|
|
161
|
+
// Iceberg / Delta
|
|
162
|
+
'iceberg_scan',
|
|
163
|
+
'iceberg_metadata',
|
|
164
|
+
'iceberg_snapshots',
|
|
165
|
+
'delta_scan',
|
|
166
|
+
// Postgres / MySQL / SQLite scanners (extension-loaded; defense-in-depth)
|
|
167
|
+
'postgres_scan',
|
|
168
|
+
'postgres_query',
|
|
169
|
+
'mysql_scan',
|
|
170
|
+
'mysql_query',
|
|
171
|
+
'sqlite_scan',
|
|
172
|
+
'sqlite_query',
|
|
173
|
+
// Spatial — pre-staged, dormant until the `spatial` extension loads.
|
|
174
|
+
// ST_Read is a GDAL-backed reader for ~50 vector formats; ST_Drivers exposes
|
|
175
|
+
// the bundled GDAL driver surface; rtree_index_dump leaks index internals.
|
|
176
|
+
// See https://github.com/cyanheads/mcp-ts-core/issues/106.
|
|
177
|
+
'st_read',
|
|
178
|
+
'st_drivers',
|
|
179
|
+
'rtree_index_dump',
|
|
180
|
+
]);
|
|
181
|
+
/**
|
|
182
|
+
* Call-shape regex (`name(`). Caller strips comments and string literals first
|
|
183
|
+
* so quoted text and comment-injected separators between a name and its `(`
|
|
184
|
+
* can't false-positive or bypass.
|
|
185
|
+
*/
|
|
186
|
+
const DENIED_FUNCTION_CALL_REGEX = new RegExp(String.raw `\b(${[...DENIED_TABLE_FUNCTIONS].join('|')})\s*\(`, 'gi');
|
|
187
|
+
/**
|
|
188
|
+
* Bare-name regex for the plan-walk rescan only. DuckDB EXPLAIN metadata can
|
|
189
|
+
* spell out a function as `Function: read_json` without parens. Restricted to
|
|
190
|
+
* known function-name fields to avoid scanning user-projected string literals.
|
|
191
|
+
*/
|
|
192
|
+
const DENIED_FUNCTION_BARE_REGEX = new RegExp(String.raw `\b(${[...DENIED_TABLE_FUNCTIONS].join('|')})\b`, 'gi');
|
|
193
|
+
/** Plan-node string fields where DuckDB stores lowered table-function names. */
|
|
194
|
+
const FUNCTION_METADATA_KEYS = new Set([
|
|
195
|
+
'extra_info',
|
|
196
|
+
'function',
|
|
197
|
+
'function_name',
|
|
198
|
+
'table_function',
|
|
199
|
+
'source',
|
|
200
|
+
]);
|
|
201
|
+
/** Strip SQL block and line comments. */
|
|
202
|
+
function stripSqlComments(sql) {
|
|
203
|
+
return sql.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/--[^\n]*/g, '');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Strip standard SQL string literals. Doesn't handle E-strings or DuckDB
|
|
207
|
+
* dollar-quoting; the plan-walk rescan catches anything that survives.
|
|
208
|
+
*/
|
|
209
|
+
function stripSqlStringLiterals(sql) {
|
|
210
|
+
return sql.replace(/'(?:[^']|'')*'/g, "''");
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Layer 1: pre-EXPLAIN function deny-list. Scans the SQL (string literals
|
|
214
|
+
* stripped) for calls to any function in {@link DENIED_TABLE_FUNCTIONS} so a
|
|
215
|
+
* malicious `read_json('/etc/passwd')` is rejected before reaching the planner.
|
|
216
|
+
*/
|
|
217
|
+
export function assertNoDeniedFunctions(sql) {
|
|
218
|
+
if (typeof sql !== 'string' || sql.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
const stripped = stripSqlStringLiterals(stripSqlComments(sql));
|
|
221
|
+
DENIED_FUNCTION_CALL_REGEX.lastIndex = 0;
|
|
222
|
+
const match = DENIED_FUNCTION_CALL_REGEX.exec(stripped);
|
|
223
|
+
if (match?.[1]) {
|
|
224
|
+
const fn = match[1].toLowerCase();
|
|
225
|
+
throw validationError(`Canvas query references disallowed table function: ${fn}. File-reading and external-data functions are not permitted.`, { reason: SQL_GATE_REASONS.deniedFunction, function: fn });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Layers 2-4. Throws on the first violation. Layer 1
|
|
230
|
+
* ({@link assertNoDeniedFunctions}) runs separately before `extractStatements`.
|
|
107
231
|
*/
|
|
108
232
|
export function assertReadOnlyQuery(input) {
|
|
109
233
|
assertSelectOnly(input);
|
|
110
234
|
assertPlanReadOnly(input.planJson);
|
|
111
235
|
}
|
|
112
236
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* here rather than a confusing parser error from EXPLAIN itself.
|
|
237
|
+
* Layers 2-3: validate statement count and type before EXPLAIN. Non-SELECT
|
|
238
|
+
* statements (ATTACH/PRAGMA/COPY/INSTALL/...) fail here with a structured
|
|
239
|
+
* ValidationError rather than a confusing parser error from EXPLAIN itself.
|
|
117
240
|
*/
|
|
118
241
|
export function assertSelectOnly(input) {
|
|
119
242
|
if (input.statementCount !== 1) {
|
|
120
243
|
throw validationError('Canvas query must contain exactly one SQL statement.', {
|
|
121
|
-
reason:
|
|
244
|
+
reason: SQL_GATE_REASONS.multiStatement,
|
|
122
245
|
statementCount: input.statementCount,
|
|
123
246
|
});
|
|
124
247
|
}
|
|
125
248
|
if (!ALLOWED_STATEMENT_TYPES.has(input.statementType)) {
|
|
126
|
-
throw validationError(`Canvas query must be SELECT; got ${input.statementType}. Mutations must use registerTable, drop, or clear.`, { reason:
|
|
249
|
+
throw validationError(`Canvas query must be SELECT; got ${input.statementType}. Mutations must use registerTable, drop, or clear.`, { reason: SQL_GATE_REASONS.nonSelectStatement, statementType: input.statementType });
|
|
127
250
|
}
|
|
128
251
|
}
|
|
129
252
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* smuggle work into a SELECT envelope.
|
|
253
|
+
* Layer 4: walk the plan tree and reject any operator outside the allowlist
|
|
254
|
+
* or any deny-listed table function smuggled into a generic scan operator.
|
|
133
255
|
*/
|
|
134
256
|
export function assertPlanReadOnly(planJson) {
|
|
135
|
-
const offending =
|
|
257
|
+
const { offending, deniedFunctions } = collectPlanViolations(planJson);
|
|
258
|
+
if (deniedFunctions.size > 0) {
|
|
259
|
+
throw validationError(`Canvas query references disallowed table function in plan: ${[...deniedFunctions].sort().join(', ')}.`, {
|
|
260
|
+
reason: SQL_GATE_REASONS.deniedFunctionInPlan,
|
|
261
|
+
functions: [...deniedFunctions].sort(),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
136
264
|
if (offending.size > 0) {
|
|
137
265
|
throw validationError(`Canvas query contains disallowed operators: ${[...offending].sort().join(', ')}.`, {
|
|
138
|
-
reason:
|
|
266
|
+
reason: SQL_GATE_REASONS.planOperatorNotAllowed,
|
|
139
267
|
operators: [...offending].sort(),
|
|
140
268
|
});
|
|
141
269
|
}
|
|
142
270
|
}
|
|
143
271
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* Tolerant of structural variation — DuckDB emits operator identity under
|
|
149
|
-
* either `name` (logical plan) or `operator_type` (physical/profile plan)
|
|
150
|
-
* depending on the EXPLAIN flavor. We honor both. Children traversal
|
|
151
|
-
* supports `children`, `child`, and `inputs` arrays.
|
|
272
|
+
* Returns operator names not in `ALLOWED_PLAN_OPERATORS`. Exported for
|
|
273
|
+
* fixture-driven tests that want to inspect the gate's view without throwing.
|
|
274
|
+
* Use {@link collectPlanViolations} to also surface deny-listed function
|
|
275
|
+
* references in scan-operator metadata.
|
|
152
276
|
*/
|
|
153
277
|
export function collectDisallowedOperators(planJson) {
|
|
278
|
+
return collectPlanViolations(planJson).offending;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Combined plan-walk: disallowed operators and deny-listed table-function
|
|
282
|
+
* references in string-valued fields, returned separately so callers can word
|
|
283
|
+
* errors appropriately.
|
|
284
|
+
*/
|
|
285
|
+
export function collectPlanViolations(planJson) {
|
|
154
286
|
const offending = new Set();
|
|
155
|
-
|
|
156
|
-
|
|
287
|
+
const deniedFunctions = new Set();
|
|
288
|
+
walk(planJson, offending, deniedFunctions);
|
|
289
|
+
return { offending, deniedFunctions };
|
|
157
290
|
}
|
|
158
|
-
function walk(node, offending) {
|
|
291
|
+
function walk(node, offending, deniedFunctions) {
|
|
159
292
|
if (node === null || typeof node !== 'object')
|
|
160
293
|
return;
|
|
161
294
|
if (Array.isArray(node)) {
|
|
162
295
|
for (const child of node)
|
|
163
|
-
walk(child, offending);
|
|
296
|
+
walk(child, offending, deniedFunctions);
|
|
164
297
|
return;
|
|
165
298
|
}
|
|
166
299
|
const obj = node;
|
|
@@ -168,19 +301,38 @@ function walk(node, offending) {
|
|
|
168
301
|
if (operator !== undefined && !ALLOWED_PLAN_OPERATORS.has(operator)) {
|
|
169
302
|
offending.add(operator);
|
|
170
303
|
}
|
|
171
|
-
//
|
|
304
|
+
// read_json/read_parquet lower into generic scan operators whose source
|
|
305
|
+
// function appears in plan metadata, not the operator name. Use the bare
|
|
306
|
+
// regex on known function-name fields, the call-shape regex elsewhere.
|
|
307
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
308
|
+
if (typeof value !== 'string')
|
|
309
|
+
continue;
|
|
310
|
+
const regex = FUNCTION_METADATA_KEYS.has(key)
|
|
311
|
+
? DENIED_FUNCTION_BARE_REGEX
|
|
312
|
+
: DENIED_FUNCTION_CALL_REGEX;
|
|
313
|
+
collectDeniedFunctionMatches(value, regex, deniedFunctions);
|
|
314
|
+
}
|
|
172
315
|
for (const key of ['children', 'child', 'inputs', 'plan', 'root']) {
|
|
173
316
|
if (key in obj)
|
|
174
|
-
walk(obj[key], offending);
|
|
317
|
+
walk(obj[key], offending, deniedFunctions);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function collectDeniedFunctionMatches(value, regex, sink) {
|
|
321
|
+
regex.lastIndex = 0;
|
|
322
|
+
let match = regex.exec(value);
|
|
323
|
+
while (match !== null) {
|
|
324
|
+
if (match[1])
|
|
325
|
+
sink.add(match[1].toLowerCase());
|
|
326
|
+
match = regex.exec(value);
|
|
175
327
|
}
|
|
176
328
|
}
|
|
177
329
|
function readOperatorName(obj) {
|
|
178
|
-
|
|
179
|
-
|
|
330
|
+
// DuckDB emits operator identity under different keys depending on the
|
|
331
|
+
// EXPLAIN flavor (logical/physical/profile).
|
|
332
|
+
for (const key of ['name', 'operator_type', 'operator', 'type']) {
|
|
180
333
|
const value = obj[key];
|
|
181
|
-
if (typeof value === 'string' && value !== '')
|
|
334
|
+
if (typeof value === 'string' && value !== '')
|
|
182
335
|
return value.toUpperCase();
|
|
183
|
-
}
|
|
184
336
|
}
|
|
185
337
|
return;
|
|
186
338
|
}
|
|
@@ -188,16 +340,15 @@ function readOperatorName(obj) {
|
|
|
188
340
|
// Identifier validation and quoting
|
|
189
341
|
// ---------------------------------------------------------------------------
|
|
190
342
|
/**
|
|
191
|
-
* Allowed shape for canvas
|
|
192
|
-
*
|
|
193
|
-
*
|
|
343
|
+
* Allowed shape for canvas table/column names. SQL identifier convention:
|
|
344
|
+
* letter/underscore start, letters/digits/underscores after, max 63 chars
|
|
345
|
+
* (PostgreSQL/DuckDB cap). Exported so consumers can reuse it in Zod schemas
|
|
346
|
+
* (`z.string().regex(CANVAS_IDENTIFIER_REGEX)`).
|
|
194
347
|
*/
|
|
195
|
-
const
|
|
348
|
+
export const CANVAS_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
196
349
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* time rather than confusing-error time. The `IDENTIFIER_REGEX` is the
|
|
200
|
-
* authoritative shape gate.
|
|
350
|
+
* Courtesy guard against bare reserved words. Not exhaustive — the shape gate
|
|
351
|
+
* is authoritative; this just produces a friendlier error at register time.
|
|
201
352
|
*/
|
|
202
353
|
const RESERVED_IDENTIFIERS = new Set([
|
|
203
354
|
'select',
|
|
@@ -237,29 +388,25 @@ const RESERVED_IDENTIFIERS = new Set([
|
|
|
237
388
|
'with',
|
|
238
389
|
'recursive',
|
|
239
390
|
]);
|
|
240
|
-
/**
|
|
241
|
-
* Validate an identifier for use as a canvas-local table or column name.
|
|
242
|
-
* Throws `ValidationError` on rejection.
|
|
243
|
-
*/
|
|
391
|
+
/** Validate a canvas table or column name. Throws `ValidationError` on rejection. */
|
|
244
392
|
export function assertValidIdentifier(value, kind) {
|
|
245
393
|
if (typeof value !== 'string' || value.length === 0) {
|
|
246
394
|
throw validationError(`Canvas ${kind} name must be a non-empty string.`, {
|
|
247
|
-
reason:
|
|
395
|
+
reason: SQL_GATE_REASONS.identifierEmpty,
|
|
248
396
|
kind,
|
|
249
397
|
});
|
|
250
398
|
}
|
|
251
|
-
if (!
|
|
252
|
-
throw validationError(`Canvas ${kind} name "${value}" is invalid. Use letters, digits, and underscores; must start with a letter or underscore; max 63 chars.`, { reason:
|
|
399
|
+
if (!CANVAS_IDENTIFIER_REGEX.test(value)) {
|
|
400
|
+
throw validationError(`Canvas ${kind} name "${value}" is invalid. Use letters, digits, and underscores; must start with a letter or underscore; max 63 chars.`, { reason: SQL_GATE_REASONS.identifierShape, kind, value });
|
|
253
401
|
}
|
|
254
402
|
if (RESERVED_IDENTIFIERS.has(value.toLowerCase())) {
|
|
255
|
-
throw validationError(`Canvas ${kind} name "${value}" is a reserved SQL keyword. Choose another name.`, { reason:
|
|
403
|
+
throw validationError(`Canvas ${kind} name "${value}" is a reserved SQL keyword. Choose another name.`, { reason: SQL_GATE_REASONS.identifierReserved, kind, value });
|
|
256
404
|
}
|
|
257
405
|
}
|
|
258
406
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* only escapes; it does not validate shape.
|
|
407
|
+
* Double-quote-escape an identifier for SQL embedding. Validate via
|
|
408
|
+
* {@link assertValidIdentifier} first — this helper only escapes, it does not
|
|
409
|
+
* check shape.
|
|
263
410
|
*/
|
|
264
411
|
export function quoteIdentifier(value) {
|
|
265
412
|
return `"${value.replace(/"/g, '""')}"`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlGate.js","sourceRoot":"","sources":["../../../../src/services/canvas/core/sqlGate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sqlGate.js","sourceRoot":"","sources":["../../../../src/services/canvas/core/sqlGate.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ export { type AcquireResult, CanvasRegistry, type CanvasRegistryOptions, DEFAULT
|
|
|
15
15
|
export { createCanvasService } from './core/canvasFactory.js';
|
|
16
16
|
export { DataCanvas } from './core/DataCanvas.js';
|
|
17
17
|
export type { IDataCanvasProvider } from './core/IDataCanvasProvider.js';
|
|
18
|
-
export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertReadOnlyQuery, assertValidIdentifier, collectDisallowedOperators, type DuckdbStatementType, quoteIdentifier, } from './core/sqlGate.js';
|
|
18
|
+
export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertNoDeniedFunctions, assertReadOnlyQuery, assertValidIdentifier, CANVAS_IDENTIFIER_REGEX, collectDisallowedOperators, collectPlanViolations, DENIED_TABLE_FUNCTIONS, type DuckdbStatementType, quoteIdentifier, SQL_GATE_REASONS, type SqlGateReason, } from './core/sqlGate.js';
|
|
19
19
|
export { DuckdbProvider, type DuckdbProviderOptions, } from './providers/duckdb/DuckdbProvider.js';
|
|
20
|
-
export type { AcquireOptions, ColumnSchema, ColumnType, DescribeOptions, ExportFormat, ExportOptions, ExportResult, ExportTarget, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, TableInfo, } from './types.js';
|
|
20
|
+
export type { AcquireOptions, CanvasObjectKind, ColumnSchema, ColumnType, DescribeOptions, ExportFormat, ExportOptions, ExportResult, ExportTarget, ImportFromOptions, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, RegisterViewOptions, RegisterViewResult, TableInfo, } from './types.js';
|
|
21
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,qBAAqB,EAC1B,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,qBAAqB,EAC1B,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,eAAe,EACf,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,SAAS,GACV,MAAM,YAAY,CAAC"}
|
|
@@ -14,6 +14,6 @@ export { CanvasInstance } from './core/CanvasInstance.js';
|
|
|
14
14
|
export { CanvasRegistry, DEFAULT_CANVAS_REGISTRY_OPTIONS, } from './core/CanvasRegistry.js';
|
|
15
15
|
export { createCanvasService } from './core/canvasFactory.js';
|
|
16
16
|
export { DataCanvas } from './core/DataCanvas.js';
|
|
17
|
-
export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertReadOnlyQuery, assertValidIdentifier, collectDisallowedOperators, quoteIdentifier, } from './core/sqlGate.js';
|
|
17
|
+
export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertNoDeniedFunctions, assertReadOnlyQuery, assertValidIdentifier, CANVAS_IDENTIFIER_REGEX, collectDisallowedOperators, collectPlanViolations, DENIED_TABLE_FUNCTIONS, quoteIdentifier, SQL_GATE_REASONS, } from './core/sqlGate.js';
|
|
18
18
|
export { DuckdbProvider, } from './providers/duckdb/DuckdbProvider.js';
|
|
19
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAEL,cAAc,EAEd,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAEL,cAAc,EAEd,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EAEtB,eAAe,EACf,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,GAEf,MAAM,sCAAsC,CAAC"}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview DuckDB-backed
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Lazy-loaded via {@link lazyImport} so `@duckdb/node-api` stays a true peer
|
|
9
|
-
* dependency — servers that don't enable canvas pay no install cost.
|
|
10
|
-
*
|
|
2
|
+
* @fileoverview DuckDB-backed {@link IDataCanvasProvider}. One in-memory
|
|
3
|
+
* DuckDB instance per canvasId; a long-lived control connection for DDL and
|
|
4
|
+
* describe operations, plus a per-query connection so cancellation interrupts
|
|
5
|
+
* exactly the in-flight query. `@duckdb/node-api` is lazy-loaded so it stays
|
|
6
|
+
* a true peer dependency.
|
|
11
7
|
* @module src/services/canvas/providers/duckdb/DuckdbProvider
|
|
12
8
|
*/
|
|
13
9
|
import { type RequestContext } from '../../../../utils/internal/requestContext.js';
|
|
14
10
|
import type { IDataCanvasProvider } from '../../core/IDataCanvasProvider.js';
|
|
15
|
-
import type { DescribeOptions, ExportOptions, ExportResult, ExportTarget, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, TableInfo } from '../../types.js';
|
|
11
|
+
import type { DescribeOptions, ExportOptions, ExportResult, ExportTarget, ImportFromOptions, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, RegisterViewOptions, RegisterViewResult, TableInfo } from '../../types.js';
|
|
16
12
|
/** Configuration for {@link DuckdbProvider}. Mirrors the AppConfig.canvas block. */
|
|
17
13
|
export interface DuckdbProviderOptions {
|
|
18
14
|
/** Default row cap for `query()` results. */
|
|
@@ -24,12 +20,9 @@ export interface DuckdbProviderOptions {
|
|
|
24
20
|
/** Number of rows to sniff for schema inference. */
|
|
25
21
|
schemaSniffRows: number;
|
|
26
22
|
}
|
|
27
|
-
/** DuckDB SELECT statement-type id (matches `StatementType.SELECT === 1`). */
|
|
28
|
-
declare const STATEMENT_TYPE_SELECT_ID = 1;
|
|
29
23
|
export declare class DuckdbProvider implements IDataCanvasProvider {
|
|
30
24
|
private readonly options;
|
|
31
25
|
readonly name = "duckdb";
|
|
32
|
-
private duck;
|
|
33
26
|
private readonly canvases;
|
|
34
27
|
constructor(options: DuckdbProviderOptions);
|
|
35
28
|
initCanvas(canvasId: string, _context: RequestContext): Promise<void>;
|
|
@@ -39,18 +32,67 @@ export declare class DuckdbProvider implements IDataCanvasProvider {
|
|
|
39
32
|
registerTable(canvasId: string, name: string, rows: RegisterRows, _context: RequestContext, options?: RegisterTableOptions): Promise<RegisterTableResult>;
|
|
40
33
|
query(canvasId: string, sql: string, _context: RequestContext, options?: QueryOptions): Promise<QueryResult>;
|
|
41
34
|
export(canvasId: string, tableName: string, target: ExportTarget, _context: RequestContext, options?: ExportOptions): Promise<ExportResult>;
|
|
35
|
+
registerView(canvasId: string, name: string, selectSql: string, _context: RequestContext, options?: RegisterViewOptions): Promise<RegisterViewResult>;
|
|
36
|
+
importFrom(targetCanvasId: string, sourceCanvasId: string, sourceTableName: string, asName: string, _context: RequestContext, options?: ImportFromOptions): Promise<RegisterTableResult>;
|
|
42
37
|
describe(canvasId: string, _context: RequestContext, options?: DescribeOptions): Promise<TableInfo[]>;
|
|
38
|
+
private describeOne;
|
|
43
39
|
drop(canvasId: string, name: string, _context: RequestContext): Promise<boolean>;
|
|
44
40
|
clear(canvasId: string, _context: RequestContext): Promise<number>;
|
|
45
41
|
private requireCanvas;
|
|
46
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Run the same four-layer read-only gate `query()` enforces, against an
|
|
44
|
+
* arbitrary SELECT string. Used by `query()` and `registerView()` so view
|
|
45
|
+
* definitions inherit query-level safety.
|
|
46
|
+
*/
|
|
47
|
+
private assertReadOnlySql;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve whether a name on the canvas refers to a base table, a view, or
|
|
50
|
+
* nothing. Returns `undefined` when absent; used by drop/registerView/
|
|
51
|
+
* importFrom to dispatch the right DDL.
|
|
52
|
+
*/
|
|
53
|
+
private lookupKind;
|
|
54
|
+
/**
|
|
55
|
+
* Materialize `COUNT(*)` against a (validated) table or view name. DuckDB
|
|
56
|
+
* returns BIGINT as a JSON string; this helper centralizes the `Number(...)`
|
|
57
|
+
* coercion so callers see a plain `number`.
|
|
58
|
+
*/
|
|
59
|
+
private countRows;
|
|
47
60
|
private runExplain;
|
|
48
61
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Coerce a value to BigInt without precision loss. `BigInt(Number(value))`
|
|
64
|
+
* round-trips through JS Number and truncates outside the 53-bit safe range,
|
|
65
|
+
* silently corrupting BIGINT IDs returned as numeric strings.
|
|
66
|
+
*
|
|
67
|
+
* @internal Exported for unit testing.
|
|
68
|
+
*/
|
|
69
|
+
export declare function toBigInt(value: unknown): bigint;
|
|
70
|
+
/**
|
|
71
|
+
* Coerce to DuckDB's TIMESTAMP unit (micros since 1970-01-01 UTC, as `bigint`).
|
|
72
|
+
* Accepts `Date`, `bigint` (already-micros), `number` (ms-since-epoch matching
|
|
73
|
+
* `Date.getTime()`), and ISO 8601 strings. Throws `validationError` otherwise.
|
|
74
|
+
*
|
|
75
|
+
* @internal Exported for unit testing.
|
|
76
|
+
*/
|
|
77
|
+
export declare function toTimestampMicros(value: unknown, columnName: string): bigint;
|
|
78
|
+
/**
|
|
79
|
+
* Coerce to DuckDB's DATE unit (days since 1970-01-01 UTC, as `number`).
|
|
80
|
+
* Accepts the same shapes as {@link toTimestampMicros}; throws otherwise.
|
|
81
|
+
*
|
|
82
|
+
* @internal Exported for unit testing.
|
|
83
|
+
*/
|
|
84
|
+
export declare function toDateDays(value: unknown, columnName: string): number;
|
|
85
|
+
/**
|
|
86
|
+
* Coerce to `Uint8Array` for BLOB appends. Accepts `Uint8Array` (Node's
|
|
87
|
+
* `Buffer` passes through as a subclass), `ArrayBuffer`, and any other
|
|
88
|
+
* `ArrayBufferView`. Throws `validationError` for non-binary inputs.
|
|
89
|
+
*
|
|
90
|
+
* @internal Exported for unit testing.
|
|
91
|
+
*/
|
|
92
|
+
export declare function toUint8Array(value: unknown, columnName: string): Uint8Array;
|
|
51
93
|
/**
|
|
52
94
|
* Map a DuckDB-thrown error to a framework error class.
|
|
53
|
-
* @internal Exported for unit testing
|
|
95
|
+
* @internal Exported for unit testing.
|
|
54
96
|
*/
|
|
55
97
|
export declare function classifyDuckdbError(err: unknown): Error;
|
|
56
98
|
//# sourceMappingURL=DuckdbProvider.d.ts.map
|