@kaelio/ktx 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/kaelio_ktx-0.16.0-py3-none-any.whl +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/setup-commands.js +1 -0
- package/dist/connection-drivers.d.ts +1 -1
- package/dist/connection-drivers.js +2 -0
- package/dist/connection.js +1 -0
- package/dist/connectors/athena/connector.d.ts +160 -0
- package/dist/connectors/athena/connector.js +378 -0
- package/dist/connectors/athena/dialect.d.ts +31 -0
- package/dist/connectors/athena/dialect.js +146 -0
- package/dist/connectors/athena/live-database-introspection.d.ts +10 -0
- package/dist/connectors/athena/live-database-introspection.js +24 -0
- package/dist/connectors/duckdb/connector.d.ts +60 -0
- package/dist/connectors/duckdb/connector.js +282 -0
- package/dist/connectors/duckdb/dialect.d.ts +31 -0
- package/dist/connectors/duckdb/dialect.js +155 -0
- package/dist/connectors/duckdb/federated-attach.js +7 -0
- package/dist/connectors/duckdb/federated-executor.js +7 -13
- package/dist/connectors/duckdb/live-database-introspection.d.ts +8 -0
- package/dist/connectors/duckdb/live-database-introspection.js +24 -0
- package/dist/connectors/shared/duckdb-json-safe.d.ts +3 -0
- package/dist/connectors/shared/duckdb-json-safe.js +12 -0
- package/dist/context/connections/connection-type-dialect.d.ts +2 -0
- package/dist/context/connections/connection-type-dialect.js +20 -0
- package/dist/context/connections/connection-type.d.ts +6 -15
- package/dist/context/connections/connection-type.js +1 -10
- package/dist/context/connections/dialects.js +4 -0
- package/dist/context/connections/drivers.js +38 -0
- package/dist/context/connections/federation.d.ts +1 -1
- package/dist/context/connections/federation.js +6 -5
- package/dist/context/connections/local-warehouse-descriptor.d.ts +1 -0
- package/dist/context/connections/local-warehouse-descriptor.js +10 -0
- package/dist/context/connections/project-sql-executor.d.ts +24 -0
- package/dist/context/connections/project-sql-executor.js +56 -0
- package/dist/context/connections/query-policy.d.ts +12 -0
- package/dist/context/connections/query-policy.js +46 -0
- package/dist/context/core/config-reference.js +11 -1
- package/dist/context/daemon/semantic-layer-compute.d.ts +16 -0
- package/dist/context/daemon/semantic-layer-compute.js +44 -2
- package/dist/context/ingest/adapters/looker/mapping.d.ts +1 -0
- package/dist/context/ingest/adapters/looker/mapping.js +3 -10
- package/dist/context/ingest/adapters/looker/types.d.ts +6 -15
- package/dist/context/ingest/adapters/metabase/mapping.d.ts +1 -0
- package/dist/context/ingest/adapters/metabase/mapping.js +1 -0
- package/dist/context/mcp/context-tools.js +1 -0
- package/dist/context/mcp/local-project-ports.d.ts +1 -1
- package/dist/context/mcp/local-project-ports.js +53 -51
- package/dist/context/project/config.d.ts +92 -0
- package/dist/context/project/driver-schemas.d.ts +46 -0
- package/dist/context/project/driver-schemas.js +8 -0
- package/dist/context/scan/local-scan.js +3 -1
- package/dist/context/scan/types.d.ts +1 -1
- package/dist/context/sl/local-query.js +23 -6
- package/dist/context/sl/semantic-layer.service.d.ts +0 -4
- package/dist/context/sl/semantic-layer.service.js +3 -20
- package/dist/context/sl/tools/sl-warehouse-validation.d.ts +1 -1
- package/dist/context/sl/tools/sl-warehouse-validation.js +2 -2
- package/dist/context/sl/types.d.ts +1 -0
- package/dist/context/sql-analysis/dialect-notes.d.ts +1 -1
- package/dist/context/sql-analysis/dialect-notes.js +3 -2
- package/dist/context/sql-analysis/dialect.js +1 -0
- package/dist/context/sql-analysis/dialects/athena.md +12 -0
- package/dist/context/sql-analysis/dialects/duckdb.md +10 -0
- package/dist/local-adapters.js +17 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +52 -2
- package/dist/sql.js +9 -14
- package/dist/status-project.js +2 -1
- package/dist/telemetry/events.d.ts +1 -1
- package/package.json +3 -1
- package/assets/python/kaelio_ktx-0.15.0-py3-none-any.whl +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
+
import { KtxExpectedError } from '../../errors.js';
|
|
4
5
|
/** @internal */
|
|
5
6
|
export function resolveKtxHomePath(path) {
|
|
6
7
|
if (path === '~') {
|
|
@@ -22,7 +23,16 @@ export function resolveKtxConfigReference(value, env) {
|
|
|
22
23
|
}
|
|
23
24
|
if (value.startsWith('file:')) {
|
|
24
25
|
const filePath = resolveKtxHomePath(value.slice('file:'.length).trim());
|
|
25
|
-
|
|
26
|
+
let fileValue;
|
|
27
|
+
try {
|
|
28
|
+
fileValue = readFileSync(filePath, 'utf8').trim();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// A `file:` secret whose target is missing or unreadable is a config
|
|
32
|
+
// problem (secrets/ is gitignored, so absent after a clone), not a ktx
|
|
33
|
+
// fault — classify it so it stays out of Error Tracking.
|
|
34
|
+
throw new KtxExpectedError(`Could not read the secret file referenced in ktx.yaml: ${filePath}`, { cause: error });
|
|
35
|
+
}
|
|
26
36
|
return fileValue.length > 0 ? fileValue : undefined;
|
|
27
37
|
}
|
|
28
38
|
const trimmed = value.trim();
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { ResolvedSemanticLayerSource, SemanticLayerQueryInput } from '../sl/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A ktx-daemon compute call failed. `inputRejected` is true when the daemon
|
|
4
|
+
* refused the request as invalid (a caller-driven outcome) rather than faulting;
|
|
5
|
+
* callers can promote those to an expected KtxQueryError while letting genuine
|
|
6
|
+
* daemon crashes reach Error Tracking. `detail` is the daemon's own message,
|
|
7
|
+
* unwrapped for surfacing to the caller.
|
|
8
|
+
*/
|
|
9
|
+
export declare class KtxDaemonComputeError extends Error {
|
|
10
|
+
readonly inputRejected: boolean;
|
|
11
|
+
readonly detail: string;
|
|
12
|
+
constructor(message: string, options: {
|
|
13
|
+
inputRejected: boolean;
|
|
14
|
+
detail: string;
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
2
18
|
interface KtxSemanticLayerComputeQueryResult {
|
|
3
19
|
sql: string;
|
|
4
20
|
dialect: string;
|
|
@@ -2,6 +2,41 @@ import { request as httpRequest } from 'node:http';
|
|
|
2
2
|
import { request as httpsRequest } from 'node:https';
|
|
3
3
|
import { URL } from 'node:url';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
+
// The daemon signals "the caller sent a well-formed request I refused as
|
|
6
|
+
// invalid" with a distinct subprocess exit code (INPUT_REJECTED_EXIT_CODE in
|
|
7
|
+
// python/ktx-daemon/__main__.py) or HTTP 400. Kept in sync across the boundary.
|
|
8
|
+
const DAEMON_INPUT_REJECTED_EXIT_CODE = 3;
|
|
9
|
+
const DAEMON_INPUT_REJECTED_HTTP_STATUS = 400;
|
|
10
|
+
/**
|
|
11
|
+
* A ktx-daemon compute call failed. `inputRejected` is true when the daemon
|
|
12
|
+
* refused the request as invalid (a caller-driven outcome) rather than faulting;
|
|
13
|
+
* callers can promote those to an expected KtxQueryError while letting genuine
|
|
14
|
+
* daemon crashes reach Error Tracking. `detail` is the daemon's own message,
|
|
15
|
+
* unwrapped for surfacing to the caller.
|
|
16
|
+
*/
|
|
17
|
+
export class KtxDaemonComputeError extends Error {
|
|
18
|
+
inputRejected;
|
|
19
|
+
detail;
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
|
|
22
|
+
this.name = 'KtxDaemonComputeError';
|
|
23
|
+
this.inputRejected = options.inputRejected;
|
|
24
|
+
this.detail = options.detail;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The daemon reports HTTP failures as `{ "detail": "…" }`; fall back to the raw body. */
|
|
28
|
+
function daemonHttpErrorDetail(raw) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.detail === 'string') {
|
|
32
|
+
return parsed.detail;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Non-JSON body (e.g. a proxy error page) — surface it verbatim.
|
|
37
|
+
}
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
5
40
|
function parseJsonObject(raw, subcommand) {
|
|
6
41
|
const parsed = JSON.parse(raw);
|
|
7
42
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
@@ -25,7 +60,11 @@ function runProcessJson(options) {
|
|
|
25
60
|
const stdoutText = Buffer.concat(stdout).toString('utf8').trim();
|
|
26
61
|
const stderrText = Buffer.concat(stderr).toString('utf8').trim();
|
|
27
62
|
if (code !== 0) {
|
|
28
|
-
|
|
63
|
+
const detail = stderrText || `exit code ${code}`;
|
|
64
|
+
reject(new KtxDaemonComputeError(`ktx-daemon ${subcommand} failed: ${detail}`, {
|
|
65
|
+
inputRejected: code === DAEMON_INPUT_REJECTED_EXIT_CODE,
|
|
66
|
+
detail,
|
|
67
|
+
}));
|
|
29
68
|
return;
|
|
30
69
|
}
|
|
31
70
|
try {
|
|
@@ -60,7 +99,10 @@ function postJson(baseUrl) {
|
|
|
60
99
|
const text = Buffer.concat(chunks).toString('utf8');
|
|
61
100
|
const statusCode = response.statusCode ?? 0;
|
|
62
101
|
if (statusCode < 200 || statusCode >= 300) {
|
|
63
|
-
reject(new
|
|
102
|
+
reject(new KtxDaemonComputeError(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`, {
|
|
103
|
+
inputRejected: statusCode === DAEMON_INPUT_REJECTED_HTTP_STATUS,
|
|
104
|
+
detail: daemonHttpErrorDetail(text),
|
|
105
|
+
}));
|
|
64
106
|
return;
|
|
65
107
|
}
|
|
66
108
|
try {
|
|
@@ -10,6 +10,7 @@ declare const LOOKER_DIALECT_TO_CONNECTION_TYPE: {
|
|
|
10
10
|
readonly sqlite: "SQLITE";
|
|
11
11
|
readonly sqlserver: "SQLSERVER";
|
|
12
12
|
readonly clickhouse: "CLICKHOUSE";
|
|
13
|
+
readonly awsathena: "ATHENA";
|
|
13
14
|
};
|
|
14
15
|
/** @internal */
|
|
15
16
|
export type LookerWarehouseTargetConnectionType = (typeof LOOKER_DIALECT_TO_CONNECTION_TYPE)[keyof typeof LOOKER_DIALECT_TO_CONNECTION_TYPE];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warehouseTargetDialect } from '../../../connections/connection-type-dialect.js';
|
|
1
2
|
const LOOKER_DIALECT_TO_CONNECTION_TYPE = {
|
|
2
3
|
bigquery: 'BIGQUERY',
|
|
3
4
|
bigquery_standard_sql: 'BIGQUERY',
|
|
@@ -7,15 +8,7 @@ const LOOKER_DIALECT_TO_CONNECTION_TYPE = {
|
|
|
7
8
|
sqlite: 'SQLITE',
|
|
8
9
|
sqlserver: 'SQLSERVER',
|
|
9
10
|
clickhouse: 'CLICKHOUSE',
|
|
10
|
-
|
|
11
|
-
const SQLGLOT_DIALECT_BY_CONNECTION_TYPE = {
|
|
12
|
-
BIGQUERY: 'bigquery',
|
|
13
|
-
SNOWFLAKE: 'snowflake',
|
|
14
|
-
POSTGRESQL: 'postgres',
|
|
15
|
-
MYSQL: 'mysql',
|
|
16
|
-
SQLITE: 'sqlite',
|
|
17
|
-
SQLSERVER: 'tsql',
|
|
18
|
-
CLICKHOUSE: 'clickhouse',
|
|
11
|
+
awsathena: 'ATHENA',
|
|
19
12
|
};
|
|
20
13
|
export async function discoverLookerConnections(client) {
|
|
21
14
|
return client.listLookerConnections();
|
|
@@ -29,7 +22,7 @@ export function lookerDialectToConnectionType(dialect) {
|
|
|
29
22
|
}
|
|
30
23
|
/** @internal */
|
|
31
24
|
export function sqlglotDialectForConnectionType(connectionType) {
|
|
32
|
-
return
|
|
25
|
+
return warehouseTargetDialect(connectionType);
|
|
33
26
|
}
|
|
34
27
|
/** @internal */
|
|
35
28
|
export function validateLookerWarehouseTarget(connectionType) {
|
|
@@ -12,27 +12,18 @@ export declare const lookerPullConfigSchema: z.ZodObject<{
|
|
|
12
12
|
lookUpdatedSince: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
13
13
|
connectionMappings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
14
14
|
connectionTypes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
15
|
-
|
|
15
|
+
POSTGRESQL: "POSTGRESQL";
|
|
16
|
+
SQLITE: "SQLITE";
|
|
17
|
+
DUCKDB: "DUCKDB";
|
|
18
|
+
SQLSERVER: "SQLSERVER";
|
|
16
19
|
BIGQUERY: "BIGQUERY";
|
|
17
20
|
SNOWFLAKE: "SNOWFLAKE";
|
|
18
|
-
MYSQL: "MYSQL";
|
|
19
|
-
SQLSERVER: "SQLSERVER";
|
|
20
|
-
SQLITE: "SQLITE";
|
|
21
|
-
CLICKHOUSE: "CLICKHOUSE";
|
|
22
|
-
POSTGRESQL: "POSTGRESQL";
|
|
23
|
-
CENTRALREACH: "CENTRALREACH";
|
|
24
|
-
EPIC: "EPIC";
|
|
25
|
-
CERNER: "CERNER";
|
|
26
21
|
ATHENA: "ATHENA";
|
|
27
|
-
QUICKBOOKS: "QUICKBOOKS";
|
|
28
|
-
WORKDAY: "WORKDAY";
|
|
29
|
-
REST: "REST";
|
|
30
|
-
S3: "S3";
|
|
31
|
-
SLACK: "SLACK";
|
|
32
22
|
METABASE: "METABASE";
|
|
33
23
|
LOOKER: "LOOKER";
|
|
34
24
|
NOTION: "NOTION";
|
|
35
|
-
|
|
25
|
+
MYSQL: "MYSQL";
|
|
26
|
+
CLICKHOUSE: "CLICKHOUSE";
|
|
36
27
|
}>>>;
|
|
37
28
|
parsedTargetTables: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
38
29
|
ok: z.ZodLiteral<true>;
|
|
@@ -171,6 +171,7 @@ const connectionListOutputSchema = z.object({
|
|
|
171
171
|
connectionType: z.string(),
|
|
172
172
|
members: z.array(z.string()).optional(),
|
|
173
173
|
hint: z.string().optional(),
|
|
174
|
+
queryPolicy: z.literal('semantic-layer-only').optional(),
|
|
174
175
|
})),
|
|
175
176
|
});
|
|
176
177
|
const wikiSearchOutputSchema = z.object({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { KtxSqlQueryExecutorPort } from '../../context/connections/query-executor.js';
|
|
2
2
|
import type { KtxProjectConnectionConfig } from '../../context/project/config.js';
|
|
3
3
|
import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type KtxSemanticLayerComputePort } from '../../context/daemon/semantic-layer-compute.js';
|
|
5
5
|
import type { KtxLocalProject } from '../../context/project/project.js';
|
|
6
6
|
import type { LocalScanMcpOptions } from '../../context/scan/local-scan.js';
|
|
7
7
|
import type { SqlAnalysisPort } from '../../context/sql-analysis/ports.js';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { KtxExpectedError, KtxQueryError, isNativeProgrammingFault } from '../../errors.js';
|
|
2
2
|
import { isDatabaseDriver, normalizeConnectionDriver } from '../../connection-drivers.js';
|
|
3
3
|
import { sqlDialectNotes } from '../../context/sql-analysis/dialect-notes.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { resolveConfiguredConnection } from '../../context/connections/resolve-connection.js';
|
|
4
|
+
import { executeProjectRawSql } from '../../context/connections/project-sql-executor.js';
|
|
5
|
+
import { federatedConnectionListing } from '../../context/connections/federation.js';
|
|
6
|
+
import { projectAllowsRawSql, restrictedFederatedMemberIds } from '../../context/connections/query-policy.js';
|
|
8
7
|
import { localConnectionInfoFromConfig, } from '../../context/connections/local-warehouse-descriptor.js';
|
|
8
|
+
import { KtxDaemonComputeError } from '../../context/daemon/semantic-layer-compute.js';
|
|
9
9
|
import { createKtxEntityDetailsService } from '../../context/scan/entity-details.js';
|
|
10
10
|
import { createKtxDiscoverDataService } from '../../context/search/discover.js';
|
|
11
11
|
import { sqlAnalysisDialectForDriver } from '../../context/sql-analysis/dialect.js';
|
|
@@ -15,8 +15,26 @@ import { readLocalSlSource } from '../../context/sl/local-sl.js';
|
|
|
15
15
|
import { assertSafeConnectionId } from '../../context/sl/source-files.js';
|
|
16
16
|
import { assertConfiguredConnectionId } from '../../context/connections/configured-connections.js';
|
|
17
17
|
import { readLocalKnowledgePage, searchLocalKnowledgePages } from '../wiki/local-knowledge.js';
|
|
18
|
+
/**
|
|
19
|
+
* Reclassify a query-path failure. Warehouse/driver rejections and daemon
|
|
20
|
+
* input-rejections are caller-driven outcomes (KtxQueryError, kept out of Error
|
|
21
|
+
* Tracking while preserving the underlying diagnostics); native JS faults, daemon
|
|
22
|
+
* crashes, and already-expected errors propagate unchanged so genuine ktx bugs
|
|
23
|
+
* still reach Error Tracking.
|
|
24
|
+
*/
|
|
25
|
+
function throwClassifiedQueryError(error) {
|
|
26
|
+
if (error instanceof KtxDaemonComputeError) {
|
|
27
|
+
if (error.inputRejected) {
|
|
28
|
+
throw new KtxQueryError(error.detail, { cause: error });
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
if (isNativeProgrammingFault(error) || error instanceof KtxExpectedError) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
36
|
+
}
|
|
18
37
|
async function executeValidatedReadOnlySql(project, options, input, onProgress) {
|
|
19
|
-
await onProgress?.({ progress: 0, message: 'Validating SQL' });
|
|
20
38
|
if (!options.sqlAnalysis) {
|
|
21
39
|
throw new Error('sql_execution requires parser-backed SQL validation.');
|
|
22
40
|
}
|
|
@@ -24,50 +42,22 @@ async function executeValidatedReadOnlySql(project, options, input, onProgress)
|
|
|
24
42
|
if (!createConnector) {
|
|
25
43
|
throw new Error('sql_execution requires a local scan connector factory.');
|
|
26
44
|
}
|
|
27
|
-
const
|
|
28
|
-
const connectionId = isFederated ? input.connectionId : assertSafeConnectionId(input.connectionId);
|
|
29
|
-
const connection = isFederated ? undefined : resolveConfiguredConnection(project.config, connectionId);
|
|
30
|
-
if (!isFederated) {
|
|
31
|
-
assertSqlQueryableConnection(connectionId, connection.driver);
|
|
32
|
-
}
|
|
33
|
-
const dialect = sqlAnalysisDialectForDriver(isFederated ? 'duckdb' : connection.driver);
|
|
34
|
-
const validation = await options.sqlAnalysis.validateReadOnly(input.sql, dialect);
|
|
35
|
-
if (!validation.ok) {
|
|
36
|
-
// A read-only guard rejecting the agent's SQL is an expected outcome, not a
|
|
37
|
-
// ktx fault: classify it so reportException keeps it out of Error Tracking.
|
|
38
|
-
throw new KtxQueryError(validation.error ?? 'SQL is not read-only.');
|
|
39
|
-
}
|
|
40
|
-
await onProgress?.({ progress: 0.3, message: 'Executing' });
|
|
41
|
-
const result = await executeProjectReadOnlySql({
|
|
45
|
+
const result = await executeProjectRawSql({
|
|
42
46
|
project,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
sql: input.sql,
|
|
48
|
-
maxRows: input.maxRows,
|
|
49
|
-
},
|
|
47
|
+
connectionId: input.connectionId,
|
|
48
|
+
sql: input.sql,
|
|
49
|
+
maxRows: input.maxRows,
|
|
50
|
+
sqlAnalysis: options.sqlAnalysis,
|
|
50
51
|
createConnector,
|
|
51
52
|
runId: 'mcp-sql-execution',
|
|
52
|
-
|
|
53
|
-
// A warehouse/driver rejection (e.g. the agent's SQL failed to compile) is a
|
|
54
|
-
// surfaced operational outcome, not a ktx fault: mark it expected while
|
|
55
|
-
// preserving the warehouse's own diagnostics. A native JS error (TypeError,
|
|
56
|
-
// etc.) signals a bug in connector code — let it propagate unchanged so Error
|
|
57
|
-
// Tracking still sees it.
|
|
58
|
-
if (isNativeProgrammingFault(error) || error instanceof KtxExpectedError) {
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
53
|
+
onProgress,
|
|
62
54
|
});
|
|
63
|
-
|
|
55
|
+
return {
|
|
64
56
|
headers: result.headers,
|
|
65
57
|
...(result.headerTypes ? { headerTypes: result.headerTypes } : {}),
|
|
66
58
|
rows: result.rows,
|
|
67
59
|
rowCount: result.rowCount ?? result.rows.length,
|
|
68
60
|
};
|
|
69
|
-
await onProgress?.({ progress: 1, message: `Fetched ${response.rowCount} rows` });
|
|
70
|
-
return response;
|
|
71
61
|
}
|
|
72
62
|
/** @internal Resolves a connection's dialect SQL notes; throws KtxExpectedError for an unknown or non-SQL-warehouse connection. */
|
|
73
63
|
export function resolveDialectNotesForConnection(connectionId, connection) {
|
|
@@ -92,12 +82,16 @@ export function createLocalProjectMcpContextPorts(project, options) {
|
|
|
92
82
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
93
83
|
const federated = federatedConnectionListing(project.config.connections, project.projectDir);
|
|
94
84
|
if (federated) {
|
|
85
|
+
const restricted = restrictedFederatedMemberIds(project.config, project.projectDir);
|
|
95
86
|
configured.push({
|
|
96
87
|
id: federated.id,
|
|
97
88
|
name: federated.id,
|
|
98
89
|
connectionType: 'DUCKDB',
|
|
99
90
|
members: federated.members,
|
|
100
|
-
hint:
|
|
91
|
+
hint: restricted.length > 0
|
|
92
|
+
? `Federated SQL is disabled: member connection(s) ${restricted.join(', ')} have query_policy: semantic-layer-only.`
|
|
93
|
+
: federated.hint,
|
|
94
|
+
...(restricted.length > 0 ? { queryPolicy: 'semantic-layer-only' } : {}),
|
|
101
95
|
});
|
|
102
96
|
}
|
|
103
97
|
return configured;
|
|
@@ -158,15 +152,20 @@ export function createLocalProjectMcpContextPorts(project, options) {
|
|
|
158
152
|
if (!options.semanticLayerCompute) {
|
|
159
153
|
throw new Error('sl_query requires a semantic-layer query adapter.');
|
|
160
154
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
try {
|
|
156
|
+
return await compileLocalSlQuery(project, {
|
|
157
|
+
connectionId: input.connectionId,
|
|
158
|
+
query: input.query,
|
|
159
|
+
compute: options.semanticLayerCompute,
|
|
160
|
+
execute: Boolean(options.queryExecutor),
|
|
161
|
+
maxRows: input.query.limit,
|
|
162
|
+
queryExecutor: options.queryExecutor,
|
|
163
|
+
onProgress: executionOptions?.onProgress,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
throwClassifiedQueryError(error);
|
|
168
|
+
}
|
|
170
169
|
},
|
|
171
170
|
},
|
|
172
171
|
entityDetails: {
|
|
@@ -191,7 +190,10 @@ export function createLocalProjectMcpContextPorts(project, options) {
|
|
|
191
190
|
},
|
|
192
191
|
},
|
|
193
192
|
};
|
|
194
|
-
|
|
193
|
+
// Register sql_execution only when some connection can accept raw SQL; in
|
|
194
|
+
// mixed projects the tool stays and executeProjectRawSql rejects restricted
|
|
195
|
+
// connection ids at request time.
|
|
196
|
+
if (options.sqlAnalysis && options.localScan?.createConnector && projectAllowsRawSql(project.config)) {
|
|
195
197
|
ports.sqlExecution = {
|
|
196
198
|
async execute(input, executionOptions) {
|
|
197
199
|
return executeValidatedReadOnlySql(project, options, input, executionOptions?.onProgress);
|
|
@@ -109,36 +109,82 @@ declare const connectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
109
109
|
url: z.ZodOptional<z.ZodString>;
|
|
110
110
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
111
111
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
113
|
+
"read-only-sql": "read-only-sql";
|
|
114
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
115
|
+
}>>;
|
|
112
116
|
}, z.core.$loose>, z.ZodObject<{
|
|
113
117
|
driver: z.ZodLiteral<"mysql">;
|
|
114
118
|
url: z.ZodOptional<z.ZodString>;
|
|
115
119
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
116
120
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
121
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
122
|
+
"read-only-sql": "read-only-sql";
|
|
123
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
124
|
+
}>>;
|
|
117
125
|
}, z.core.$loose>, z.ZodObject<{
|
|
118
126
|
driver: z.ZodLiteral<"snowflake">;
|
|
119
127
|
url: z.ZodOptional<z.ZodString>;
|
|
120
128
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
121
129
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
130
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
131
|
+
"read-only-sql": "read-only-sql";
|
|
132
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
133
|
+
}>>;
|
|
122
134
|
}, z.core.$loose>, z.ZodObject<{
|
|
123
135
|
driver: z.ZodLiteral<"bigquery">;
|
|
124
136
|
url: z.ZodOptional<z.ZodString>;
|
|
125
137
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
126
138
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
139
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
140
|
+
"read-only-sql": "read-only-sql";
|
|
141
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
142
|
+
}>>;
|
|
127
143
|
}, z.core.$loose>, z.ZodObject<{
|
|
128
144
|
driver: z.ZodLiteral<"sqlite">;
|
|
129
145
|
url: z.ZodOptional<z.ZodString>;
|
|
130
146
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
131
147
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
149
|
+
"read-only-sql": "read-only-sql";
|
|
150
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
151
|
+
}>>;
|
|
152
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
153
|
+
driver: z.ZodLiteral<"duckdb">;
|
|
154
|
+
url: z.ZodOptional<z.ZodString>;
|
|
155
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
156
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
157
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
158
|
+
"read-only-sql": "read-only-sql";
|
|
159
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
160
|
+
}>>;
|
|
132
161
|
}, z.core.$loose>, z.ZodObject<{
|
|
133
162
|
driver: z.ZodLiteral<"clickhouse">;
|
|
134
163
|
url: z.ZodOptional<z.ZodString>;
|
|
135
164
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
165
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
167
|
+
"read-only-sql": "read-only-sql";
|
|
168
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
169
|
+
}>>;
|
|
137
170
|
}, z.core.$loose>, z.ZodObject<{
|
|
138
171
|
driver: z.ZodLiteral<"sqlserver">;
|
|
139
172
|
url: z.ZodOptional<z.ZodString>;
|
|
140
173
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
141
174
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
175
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
176
|
+
"read-only-sql": "read-only-sql";
|
|
177
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
178
|
+
}>>;
|
|
179
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
180
|
+
driver: z.ZodLiteral<"athena">;
|
|
181
|
+
url: z.ZodOptional<z.ZodString>;
|
|
182
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
183
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
184
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
185
|
+
"read-only-sql": "read-only-sql";
|
|
186
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
187
|
+
}>>;
|
|
142
188
|
}, z.core.$loose>, z.ZodObject<{
|
|
143
189
|
driver: z.ZodLiteral<"mongodb">;
|
|
144
190
|
url: z.ZodString;
|
|
@@ -248,36 +294,82 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
248
294
|
url: z.ZodOptional<z.ZodString>;
|
|
249
295
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
250
296
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
297
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
298
|
+
"read-only-sql": "read-only-sql";
|
|
299
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
300
|
+
}>>;
|
|
251
301
|
}, z.core.$loose>, z.ZodObject<{
|
|
252
302
|
driver: z.ZodLiteral<"mysql">;
|
|
253
303
|
url: z.ZodOptional<z.ZodString>;
|
|
254
304
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
255
305
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
306
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
307
|
+
"read-only-sql": "read-only-sql";
|
|
308
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
309
|
+
}>>;
|
|
256
310
|
}, z.core.$loose>, z.ZodObject<{
|
|
257
311
|
driver: z.ZodLiteral<"snowflake">;
|
|
258
312
|
url: z.ZodOptional<z.ZodString>;
|
|
259
313
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
260
314
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
315
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
316
|
+
"read-only-sql": "read-only-sql";
|
|
317
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
318
|
+
}>>;
|
|
261
319
|
}, z.core.$loose>, z.ZodObject<{
|
|
262
320
|
driver: z.ZodLiteral<"bigquery">;
|
|
263
321
|
url: z.ZodOptional<z.ZodString>;
|
|
264
322
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
265
323
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
324
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
325
|
+
"read-only-sql": "read-only-sql";
|
|
326
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
327
|
+
}>>;
|
|
266
328
|
}, z.core.$loose>, z.ZodObject<{
|
|
267
329
|
driver: z.ZodLiteral<"sqlite">;
|
|
268
330
|
url: z.ZodOptional<z.ZodString>;
|
|
269
331
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
270
332
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
333
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
334
|
+
"read-only-sql": "read-only-sql";
|
|
335
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
336
|
+
}>>;
|
|
337
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
338
|
+
driver: z.ZodLiteral<"duckdb">;
|
|
339
|
+
url: z.ZodOptional<z.ZodString>;
|
|
340
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
341
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
342
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
343
|
+
"read-only-sql": "read-only-sql";
|
|
344
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
345
|
+
}>>;
|
|
271
346
|
}, z.core.$loose>, z.ZodObject<{
|
|
272
347
|
driver: z.ZodLiteral<"clickhouse">;
|
|
273
348
|
url: z.ZodOptional<z.ZodString>;
|
|
274
349
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
275
350
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
351
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
352
|
+
"read-only-sql": "read-only-sql";
|
|
353
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
354
|
+
}>>;
|
|
276
355
|
}, z.core.$loose>, z.ZodObject<{
|
|
277
356
|
driver: z.ZodLiteral<"sqlserver">;
|
|
278
357
|
url: z.ZodOptional<z.ZodString>;
|
|
279
358
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
280
359
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
360
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
361
|
+
"read-only-sql": "read-only-sql";
|
|
362
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
363
|
+
}>>;
|
|
364
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
365
|
+
driver: z.ZodLiteral<"athena">;
|
|
366
|
+
url: z.ZodOptional<z.ZodString>;
|
|
367
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
368
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
369
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
370
|
+
"read-only-sql": "read-only-sql";
|
|
371
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
372
|
+
}>>;
|
|
281
373
|
}, z.core.$loose>, z.ZodObject<{
|
|
282
374
|
driver: z.ZodLiteral<"mongodb">;
|
|
283
375
|
url: z.ZodString;
|
|
@@ -4,36 +4,82 @@ export declare const connectionConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
|
|
|
4
4
|
url: z.ZodOptional<z.ZodString>;
|
|
5
5
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
6
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
8
|
+
"read-only-sql": "read-only-sql";
|
|
9
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
10
|
+
}>>;
|
|
7
11
|
}, z.core.$loose>, z.ZodObject<{
|
|
8
12
|
driver: z.ZodLiteral<"mysql">;
|
|
9
13
|
url: z.ZodOptional<z.ZodString>;
|
|
10
14
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11
15
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
17
|
+
"read-only-sql": "read-only-sql";
|
|
18
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
19
|
+
}>>;
|
|
12
20
|
}, z.core.$loose>, z.ZodObject<{
|
|
13
21
|
driver: z.ZodLiteral<"snowflake">;
|
|
14
22
|
url: z.ZodOptional<z.ZodString>;
|
|
15
23
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
16
24
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
26
|
+
"read-only-sql": "read-only-sql";
|
|
27
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
28
|
+
}>>;
|
|
17
29
|
}, z.core.$loose>, z.ZodObject<{
|
|
18
30
|
driver: z.ZodLiteral<"bigquery">;
|
|
19
31
|
url: z.ZodOptional<z.ZodString>;
|
|
20
32
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
21
33
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
35
|
+
"read-only-sql": "read-only-sql";
|
|
36
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
37
|
+
}>>;
|
|
22
38
|
}, z.core.$loose>, z.ZodObject<{
|
|
23
39
|
driver: z.ZodLiteral<"sqlite">;
|
|
24
40
|
url: z.ZodOptional<z.ZodString>;
|
|
25
41
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
42
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
"read-only-sql": "read-only-sql";
|
|
45
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
46
|
+
}>>;
|
|
47
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
48
|
+
driver: z.ZodLiteral<"duckdb">;
|
|
49
|
+
url: z.ZodOptional<z.ZodString>;
|
|
50
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
53
|
+
"read-only-sql": "read-only-sql";
|
|
54
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
55
|
+
}>>;
|
|
27
56
|
}, z.core.$loose>, z.ZodObject<{
|
|
28
57
|
driver: z.ZodLiteral<"clickhouse">;
|
|
29
58
|
url: z.ZodOptional<z.ZodString>;
|
|
30
59
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
60
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
62
|
+
"read-only-sql": "read-only-sql";
|
|
63
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
64
|
+
}>>;
|
|
32
65
|
}, z.core.$loose>, z.ZodObject<{
|
|
33
66
|
driver: z.ZodLiteral<"sqlserver">;
|
|
34
67
|
url: z.ZodOptional<z.ZodString>;
|
|
35
68
|
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
69
|
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
71
|
+
"read-only-sql": "read-only-sql";
|
|
72
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
73
|
+
}>>;
|
|
74
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
75
|
+
driver: z.ZodLiteral<"athena">;
|
|
76
|
+
url: z.ZodOptional<z.ZodString>;
|
|
77
|
+
enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
78
|
+
query_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
query_policy: z.ZodOptional<z.ZodEnum<{
|
|
80
|
+
"read-only-sql": "read-only-sql";
|
|
81
|
+
"semantic-layer-only": "semantic-layer-only";
|
|
82
|
+
}>>;
|
|
37
83
|
}, z.core.$loose>, z.ZodObject<{
|
|
38
84
|
driver: z.ZodLiteral<"mongodb">;
|
|
39
85
|
url: z.ZodString;
|