@kaelio/ktx 0.13.0 → 0.14.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.13.0-py3-none-any.whl → kaelio_ktx-0.14.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +1 -1
- package/dist/commands/ingest-commands.d.ts +9 -0
- package/dist/commands/ingest-commands.js +37 -1
- package/dist/commands/knowledge-commands.js +3 -0
- package/dist/commands/setup-commands.js +14 -1
- package/dist/connection-drivers.d.ts +2 -0
- package/dist/connection-drivers.js +7 -3
- package/dist/connection.d.ts +3 -0
- package/dist/connection.js +27 -0
- package/dist/connectors/bigquery/connector.d.ts +17 -6
- package/dist/connectors/bigquery/connector.js +152 -76
- package/dist/connectors/bigquery/dialect.d.ts +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +1 -0
- package/dist/connectors/clickhouse/connector.js +45 -16
- package/dist/connectors/clickhouse/dialect.d.ts +2 -2
- package/dist/connectors/mongodb/connector.d.ts +69 -0
- package/dist/connectors/mongodb/connector.js +307 -0
- package/dist/connectors/mongodb/dialect.d.ts +17 -0
- package/dist/connectors/mongodb/dialect.js +50 -0
- package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
- package/dist/connectors/mongodb/live-database-introspection.js +24 -0
- package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
- package/dist/connectors/mongodb/schema-inference.js +110 -0
- package/dist/connectors/mysql/connector.d.ts +1 -0
- package/dist/connectors/mysql/connector.js +18 -2
- package/dist/connectors/mysql/dialect.d.ts +2 -2
- package/dist/connectors/postgres/connector.d.ts +1 -0
- package/dist/connectors/postgres/connector.js +20 -3
- package/dist/connectors/postgres/dialect.d.ts +2 -2
- package/dist/connectors/snowflake/connector.d.ts +1 -0
- package/dist/connectors/snowflake/connector.js +27 -3
- package/dist/connectors/snowflake/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/connector.d.ts +11 -1
- package/dist/connectors/sqlite/connector.js +120 -17
- package/dist/connectors/sqlite/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
- package/dist/connectors/sqlite/read-query-child.js +23 -0
- package/dist/connectors/sqlserver/connector.d.ts +2 -0
- package/dist/connectors/sqlserver/connector.js +23 -5
- package/dist/connectors/sqlserver/dialect.d.ts +2 -2
- package/dist/context/cache/content-result-cache.d.ts +36 -0
- package/dist/context/cache/content-result-cache.js +14 -0
- package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
- package/dist/context/cache/sqlite-content-result-cache.js +223 -0
- package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
- package/dist/context/connections/bigquery-identifiers.js +7 -0
- package/dist/context/connections/configured-connections.d.ts +9 -0
- package/dist/context/connections/configured-connections.js +18 -0
- package/dist/context/connections/dialects.d.ts +30 -4
- package/dist/context/connections/dialects.js +38 -11
- package/dist/context/connections/drivers.js +21 -0
- package/dist/context/connections/gdrive-config.d.ts +19 -0
- package/dist/context/connections/gdrive-config.js +57 -0
- package/dist/context/connections/query-deadline.d.ts +31 -0
- package/dist/context/connections/query-deadline.js +37 -0
- package/dist/context/connections/read-only-sql.d.ts +5 -0
- package/dist/context/connections/read-only-sql.js +139 -1
- package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
- package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
- package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
- package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
- package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
- package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
- package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
- package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
- package/dist/context/ingest/adapters/gdrive/types.js +36 -0
- package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
- package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
- package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
- package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
- package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
- package/dist/context/ingest/adapters/live-database/stage.js +5 -5
- package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/types.js +1 -1
- package/dist/context/ingest/artifact-gates.d.ts +32 -1
- package/dist/context/ingest/artifact-gates.js +85 -18
- package/dist/context/ingest/final-gate-prune.d.ts +35 -0
- package/dist/context/ingest/final-gate-prune.js +229 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +459 -240
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
- package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
- package/dist/context/ingest/local-adapters.js +6 -0
- package/dist/context/ingest/local-bundle-runtime.js +7 -2
- package/dist/context/ingest/local-ingest.js +1 -1
- package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
- package/dist/context/ingest/local-stage-ingest.js +3 -1
- package/dist/context/ingest/ports.d.ts +3 -0
- package/dist/context/ingest/report-snapshot.js +13 -3
- package/dist/context/ingest/reports.d.ts +3 -3
- package/dist/context/ingest/reports.js +3 -1
- package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
- package/dist/context/ingest/stages/build-wu-context.js +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
- package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
- package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
- package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
- package/dist/context/ingest/types.d.ts +3 -0
- package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
- package/dist/context/ingest/wiki-body-refs.js +37 -8
- package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
- package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
- package/dist/context/ingest/work-unit-cache.d.ts +67 -0
- package/dist/context/ingest/work-unit-cache.js +230 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.js +5 -0
- package/dist/context/llm/claude-code-runtime.d.ts +4 -1
- package/dist/context/llm/claude-code-runtime.js +38 -8
- package/dist/context/llm/codex-runtime.d.ts +4 -1
- package/dist/context/llm/codex-runtime.js +48 -35
- package/dist/context/llm/runtime-port.d.ts +26 -0
- package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
- package/dist/context/llm/subprocess-generate-object-child.js +34 -0
- package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
- package/dist/context/llm/subprocess-generate-object.js +122 -0
- package/dist/context/mcp/context-tools.d.ts +2 -0
- package/dist/context/mcp/context-tools.js +111 -17
- package/dist/context/mcp/local-project-ports.d.ts +7 -0
- package/dist/context/mcp/local-project-ports.js +29 -0
- package/dist/context/mcp/logger.d.ts +24 -0
- package/dist/context/mcp/logger.js +49 -0
- package/dist/context/mcp/server.js +2 -0
- package/dist/context/mcp/types.d.ts +17 -0
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +42 -0
- package/dist/context/project/config.js +5 -0
- package/dist/context/project/driver-schemas.d.ts +20 -0
- package/dist/context/project/driver-schemas.js +50 -1
- package/dist/context/project/project.js +1 -1
- package/dist/context/project/setup-config.js +1 -0
- package/dist/context/scan/description-generation.d.ts +4 -0
- package/dist/context/scan/description-generation.js +100 -13
- package/dist/context/scan/enabled-tables.d.ts +5 -0
- package/dist/context/scan/enabled-tables.js +13 -1
- package/dist/context/scan/enrichment-state.d.ts +50 -7
- package/dist/context/scan/enrichment-state.js +30 -13
- package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
- package/dist/context/scan/local-enrichment-artifacts.js +155 -32
- package/dist/context/scan/local-enrichment.d.ts +39 -4
- package/dist/context/scan/local-enrichment.js +345 -92
- package/dist/context/scan/local-scan.d.ts +4 -1
- package/dist/context/scan/local-scan.js +54 -13
- package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
- package/dist/context/scan/local-structural-artifacts.js +5 -0
- package/dist/context/scan/object-introspection.d.ts +21 -0
- package/dist/context/scan/object-introspection.js +35 -0
- package/dist/context/scan/relationship-benchmarks.js +5 -3
- package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
- package/dist/context/scan/relationship-composite-candidates.js +13 -1
- package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
- package/dist/context/scan/relationship-detection-budget.js +53 -0
- package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
- package/dist/context/scan/relationship-diagnostics.js +2 -0
- package/dist/context/scan/relationship-discovery.d.ts +9 -3
- package/dist/context/scan/relationship-discovery.js +27 -4
- package/dist/context/scan/relationship-llm-proposal.js +36 -27
- package/dist/context/scan/relationship-profiling.d.ts +7 -3
- package/dist/context/scan/relationship-profiling.js +53 -41
- package/dist/context/scan/relationship-validation.d.ts +6 -4
- package/dist/context/scan/relationship-validation.js +46 -32
- package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
- package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
- package/dist/context/scan/types.d.ts +5 -3
- package/dist/context/scan/types.js +2 -0
- package/dist/context/sl/local-query.js +5 -0
- package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
- package/dist/context/sl/semantic-layer.service.js +1 -1
- package/dist/context/sl/source-files.d.ts +5 -0
- package/dist/context/sl/source-files.js +17 -11
- package/dist/context/sl/tools/connection-id-schema.js +1 -1
- package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
- package/dist/context/sql-analysis/dialect-notes.js +40 -0
- package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
- package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
- package/dist/context/sql-analysis/dialects/mysql.md +9 -0
- package/dist/context/sql-analysis/dialects/postgres.md +10 -0
- package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
- package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
- package/dist/context/sql-analysis/dialects/tsql.md +10 -0
- package/dist/context/wiki/keys.js +1 -1
- package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
- package/dist/context/wiki/knowledge-wiki.service.js +2 -1
- package/dist/context/wiki/local-knowledge.d.ts +13 -0
- package/dist/context/wiki/local-knowledge.js +50 -6
- package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
- package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
- package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
- package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
- package/dist/context/wiki/types.d.ts +6 -0
- package/dist/context-build-view.d.ts +3 -0
- package/dist/context-build-view.js +1 -0
- package/dist/knowledge.d.ts +2 -0
- package/dist/knowledge.js +12 -1
- package/dist/local-adapters.js +11 -0
- package/dist/mcp-http-server.js +15 -1
- package/dist/mcp-server-factory.d.ts +2 -0
- package/dist/mcp-server-factory.js +15 -1
- package/dist/mcp-stdio-server.js +10 -2
- package/dist/notion-page-picker.js +1 -1
- package/dist/prompts/memory_agent_external_ingest.md +2 -0
- package/dist/public-ingest.d.ts +3 -1
- package/dist/public-ingest.js +3 -0
- package/dist/scan.d.ts +3 -1
- package/dist/scan.js +7 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +43 -1
- package/dist/setup-sources.d.ts +5 -1
- package/dist/setup-sources.js +124 -48
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +6 -1
- package/dist/skills/analytics/SKILL.md +200 -2
- package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
- package/dist/skills/wiki_capture/SKILL.md +24 -0
- package/dist/sql.js +4 -0
- package/dist/status-project.d.ts +4 -0
- package/dist/status-project.js +54 -2
- package/dist/telemetry/events.d.ts +2 -2
- package/dist/text-ingest.d.ts +4 -0
- package/dist/text-ingest.js +58 -29
- package/dist/verbatim-ingest.d.ts +46 -0
- package/dist/verbatim-ingest.js +193 -0
- package/package.json +5 -1
- package/dist/context/ingest/final-gate-repair.d.ts +0 -28
- package/dist/context/ingest/final-gate-repair.js +0 -91
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
import { resolveKtxConfigReference } from '../../context/core/config-reference.js';
|
|
3
|
+
import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
|
|
4
|
+
import { scopedTableNames } from '../../context/scan/table-ref.js';
|
|
5
|
+
import { getDialectForDriver } from '../../context/connections/dialects.js';
|
|
6
|
+
import { inferKtxMongoCollectionColumns, MONGO_ID_FIELD } from './schema-inference.js';
|
|
7
|
+
const DEFAULT_SAMPLE_SIZE = 1000;
|
|
8
|
+
const SAMPLE_MAX_TIME_MS = 30_000;
|
|
9
|
+
class DefaultMongoClient {
|
|
10
|
+
client;
|
|
11
|
+
connected = false;
|
|
12
|
+
constructor(url) {
|
|
13
|
+
this.client = new MongoClient(url);
|
|
14
|
+
}
|
|
15
|
+
async connectedClient() {
|
|
16
|
+
if (!this.connected) {
|
|
17
|
+
await this.client.connect();
|
|
18
|
+
this.connected = true;
|
|
19
|
+
}
|
|
20
|
+
return this.client;
|
|
21
|
+
}
|
|
22
|
+
async listCollections(databaseName) {
|
|
23
|
+
const client = await this.connectedClient();
|
|
24
|
+
const collections = await client.db(databaseName).listCollections({}, { nameOnly: false }).toArray();
|
|
25
|
+
return collections.map((collection) => ({ name: collection.name, type: collection.type }));
|
|
26
|
+
}
|
|
27
|
+
async estimatedDocumentCount(databaseName, collectionName) {
|
|
28
|
+
const client = await this.connectedClient();
|
|
29
|
+
return client.db(databaseName).collection(collectionName).estimatedDocumentCount();
|
|
30
|
+
}
|
|
31
|
+
async find(databaseName, collectionName, options) {
|
|
32
|
+
const client = await this.connectedClient();
|
|
33
|
+
return client
|
|
34
|
+
.db(databaseName)
|
|
35
|
+
.collection(collectionName)
|
|
36
|
+
.find({}, { sort: options.sort, limit: options.limit, maxTimeMS: SAMPLE_MAX_TIME_MS, ...(options.projection ? { projection: options.projection } : {}) })
|
|
37
|
+
.toArray();
|
|
38
|
+
}
|
|
39
|
+
async ping(databaseName) {
|
|
40
|
+
const client = await this.connectedClient();
|
|
41
|
+
await client.db(databaseName).command({ ping: 1 });
|
|
42
|
+
}
|
|
43
|
+
async close() {
|
|
44
|
+
if (this.connected) {
|
|
45
|
+
await this.client.close();
|
|
46
|
+
this.connected = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class DefaultMongoClientFactory {
|
|
51
|
+
create(url) {
|
|
52
|
+
return new DefaultMongoClient(url);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function isKtxMongoDbConnectionConfig(connection) {
|
|
56
|
+
return String(connection?.driver ?? '').toLowerCase() === 'mongodb';
|
|
57
|
+
}
|
|
58
|
+
function databaseFromUrl(url) {
|
|
59
|
+
try {
|
|
60
|
+
const path = new URL(url).pathname.replace(/^\/+/, '');
|
|
61
|
+
const database = path.split('/')[0];
|
|
62
|
+
return database && database.length > 0 ? decodeURIComponent(database) : undefined;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function configuredDatabases(connection, fallback) {
|
|
69
|
+
if (Array.isArray(connection.databases)) {
|
|
70
|
+
const selected = connection.databases
|
|
71
|
+
.filter((database) => typeof database === 'string' && database.trim().length > 0)
|
|
72
|
+
.map((database) => database.trim());
|
|
73
|
+
if (selected.length > 0) {
|
|
74
|
+
return [...new Set(selected)];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const single = typeof connection.database === 'string' && connection.database.trim().length > 0
|
|
78
|
+
? connection.database.trim()
|
|
79
|
+
: fallback;
|
|
80
|
+
return single ? [single] : [];
|
|
81
|
+
}
|
|
82
|
+
function positiveInteger(value, fallback) {
|
|
83
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
84
|
+
}
|
|
85
|
+
function normalizeSampleValue(value) {
|
|
86
|
+
if (value === null || value === undefined) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (value instanceof Date) {
|
|
90
|
+
return value.toISOString();
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'object') {
|
|
93
|
+
const bsontype = value._bsontype;
|
|
94
|
+
return typeof bsontype === 'string' ? String(value) : JSON.stringify(value);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function unionDocumentKeys(documents) {
|
|
99
|
+
const keys = [];
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
for (const document of documents) {
|
|
102
|
+
for (const key of Object.keys(document)) {
|
|
103
|
+
if (!seen.has(key)) {
|
|
104
|
+
seen.add(key);
|
|
105
|
+
keys.push(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return keys;
|
|
110
|
+
}
|
|
111
|
+
export class KtxMongoDbScanConnector {
|
|
112
|
+
id;
|
|
113
|
+
driver = 'mongodb';
|
|
114
|
+
capabilities = createKtxConnectorCapabilities({
|
|
115
|
+
tableSampling: true,
|
|
116
|
+
columnSampling: true,
|
|
117
|
+
columnStats: false,
|
|
118
|
+
readOnlySql: false,
|
|
119
|
+
nestedAnalysis: true,
|
|
120
|
+
formalForeignKeys: false,
|
|
121
|
+
estimatedRowCounts: true,
|
|
122
|
+
});
|
|
123
|
+
connectionId;
|
|
124
|
+
connection;
|
|
125
|
+
url;
|
|
126
|
+
databases;
|
|
127
|
+
sampleSize;
|
|
128
|
+
orderBy;
|
|
129
|
+
enabledTables;
|
|
130
|
+
clientFactory;
|
|
131
|
+
now;
|
|
132
|
+
dialect = getDialectForDriver('mongodb');
|
|
133
|
+
client = null;
|
|
134
|
+
constructor(options) {
|
|
135
|
+
const connection = options.connection ?? {};
|
|
136
|
+
const inputDriver = connection.driver ?? 'unknown';
|
|
137
|
+
if (!isKtxMongoDbConnectionConfig(connection)) {
|
|
138
|
+
throw new Error(`Native MongoDB connector cannot run driver "${inputDriver}"`);
|
|
139
|
+
}
|
|
140
|
+
const env = options.env ?? process.env;
|
|
141
|
+
const url = resolveKtxConfigReference(typeof connection.url === 'string' ? connection.url.trim() : undefined, env);
|
|
142
|
+
if (!url) {
|
|
143
|
+
throw new Error(`Native MongoDB connector requires connections.${options.connectionId}.url`);
|
|
144
|
+
}
|
|
145
|
+
const databases = configuredDatabases(connection, databaseFromUrl(url));
|
|
146
|
+
if (databases.length === 0) {
|
|
147
|
+
throw new Error(`Native MongoDB connector requires connections.${options.connectionId}.databases (or a database in the URL)`);
|
|
148
|
+
}
|
|
149
|
+
const enabledTables = Array.isArray(connection.enabled_tables)
|
|
150
|
+
? new Set(connection.enabled_tables
|
|
151
|
+
.filter((table) => typeof table === 'string' && table.trim().length > 0)
|
|
152
|
+
.map((table) => table.trim()))
|
|
153
|
+
: null;
|
|
154
|
+
this.connectionId = options.connectionId;
|
|
155
|
+
this.connection = connection;
|
|
156
|
+
this.url = url;
|
|
157
|
+
this.databases = databases;
|
|
158
|
+
this.sampleSize = positiveInteger(connection.sample_size, DEFAULT_SAMPLE_SIZE);
|
|
159
|
+
this.orderBy = typeof connection.order_by === 'string' && connection.order_by.trim().length > 0
|
|
160
|
+
? connection.order_by.trim()
|
|
161
|
+
: MONGO_ID_FIELD;
|
|
162
|
+
this.enabledTables = enabledTables && enabledTables.size > 0 ? enabledTables : null;
|
|
163
|
+
this.clientFactory = options.clientFactory ?? new DefaultMongoClientFactory();
|
|
164
|
+
this.now = options.now ?? (() => new Date());
|
|
165
|
+
this.id = `mongodb:${options.connectionId}`;
|
|
166
|
+
}
|
|
167
|
+
async testConnection() {
|
|
168
|
+
try {
|
|
169
|
+
await this.clientForQuery().ping(this.databases[0]);
|
|
170
|
+
return { success: true };
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return connectorTestFailure(error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async introspect(input, _ctx) {
|
|
177
|
+
this.assertConnection(input.connectionId);
|
|
178
|
+
const client = this.clientForQuery();
|
|
179
|
+
const tables = [];
|
|
180
|
+
for (const database of this.databases) {
|
|
181
|
+
const scopedNames = input.tableScope
|
|
182
|
+
? new Set(scopedTableNames(input.tableScope, { catalog: null, db: database }))
|
|
183
|
+
: null;
|
|
184
|
+
const collections = await client.listCollections(database);
|
|
185
|
+
for (const collection of collections) {
|
|
186
|
+
if (collection.name.startsWith('system.')) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (scopedNames && !scopedNames.has(collection.name)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (this.enabledTables && !this.enabledTables.has(`${database}.${collection.name}`)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
tables.push(await this.introspectCollection(client, database, collection));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
connectionId: this.connectionId,
|
|
200
|
+
driver: 'mongodb',
|
|
201
|
+
extractedAt: this.now().toISOString(),
|
|
202
|
+
scope: { schemas: this.databases },
|
|
203
|
+
metadata: {
|
|
204
|
+
databases: this.databases,
|
|
205
|
+
sample_size: this.sampleSize,
|
|
206
|
+
order_by: this.orderBy,
|
|
207
|
+
table_count: tables.length,
|
|
208
|
+
total_columns: tables.reduce((sum, table) => sum + table.columns.length, 0),
|
|
209
|
+
},
|
|
210
|
+
tables,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async introspectCollection(client, database, collection) {
|
|
214
|
+
const isView = collection.type === 'view';
|
|
215
|
+
// estimatedDocumentCount issues a count command, which MongoDB rejects on a
|
|
216
|
+
// view (CommandNotSupportedOnView); only count real collections.
|
|
217
|
+
const estimatedRows = isView ? null : await client.estimatedDocumentCount(database, collection.name);
|
|
218
|
+
const documents = await client.find(database, collection.name, {
|
|
219
|
+
sort: { [this.orderBy]: -1 },
|
|
220
|
+
limit: this.sampleSize,
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
catalog: null,
|
|
224
|
+
db: database,
|
|
225
|
+
name: collection.name,
|
|
226
|
+
kind: isView ? 'view' : 'table',
|
|
227
|
+
comment: null,
|
|
228
|
+
estimatedRows,
|
|
229
|
+
columns: inferKtxMongoCollectionColumns(documents, this.dialect),
|
|
230
|
+
foreignKeys: [],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async sampleTable(input, _ctx) {
|
|
234
|
+
this.assertConnection(input.connectionId);
|
|
235
|
+
const { database, collection } = this.resolveTableRef(input.table);
|
|
236
|
+
const documents = await this.clientForQuery().find(database, collection, {
|
|
237
|
+
sort: { [this.orderBy]: -1 },
|
|
238
|
+
limit: input.limit,
|
|
239
|
+
});
|
|
240
|
+
const headers = input.columns && input.columns.length > 0 ? input.columns : unionDocumentKeys(documents);
|
|
241
|
+
const rows = documents.map((document) => headers.map((header) => normalizeSampleValue(document[header])));
|
|
242
|
+
return { headers, rows, totalRows: documents.length };
|
|
243
|
+
}
|
|
244
|
+
async sampleColumn(input, _ctx) {
|
|
245
|
+
this.assertConnection(input.connectionId);
|
|
246
|
+
const { database, collection } = this.resolveTableRef(input.table);
|
|
247
|
+
const documents = await this.clientForQuery().find(database, collection, {
|
|
248
|
+
sort: { [this.orderBy]: -1 },
|
|
249
|
+
limit: input.limit,
|
|
250
|
+
projection: { [input.column]: 1 },
|
|
251
|
+
});
|
|
252
|
+
const values = [];
|
|
253
|
+
let nullCount = 0;
|
|
254
|
+
for (const document of documents) {
|
|
255
|
+
const value = document[input.column];
|
|
256
|
+
if (value === null || value === undefined) {
|
|
257
|
+
nullCount += 1;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
values.push(normalizeSampleValue(value));
|
|
261
|
+
}
|
|
262
|
+
return { values, nullCount, distinctCount: null };
|
|
263
|
+
}
|
|
264
|
+
async listSchemas() {
|
|
265
|
+
return [...this.databases];
|
|
266
|
+
}
|
|
267
|
+
async listTables(schemas) {
|
|
268
|
+
const client = this.clientForQuery();
|
|
269
|
+
const databases = schemas && schemas.length > 0 ? schemas : this.databases;
|
|
270
|
+
const entries = [];
|
|
271
|
+
for (const database of databases) {
|
|
272
|
+
const collections = await client.listCollections(database);
|
|
273
|
+
for (const collection of collections) {
|
|
274
|
+
if (collection.name.startsWith('system.')) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
entries.push({
|
|
278
|
+
catalog: null,
|
|
279
|
+
schema: database,
|
|
280
|
+
name: collection.name,
|
|
281
|
+
kind: collection.type === 'view' ? 'view' : 'table',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return entries;
|
|
286
|
+
}
|
|
287
|
+
async cleanup() {
|
|
288
|
+
if (this.client) {
|
|
289
|
+
await this.client.close();
|
|
290
|
+
this.client = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
resolveTableRef(table) {
|
|
294
|
+
return { database: table.db ?? this.databases[0], collection: table.name };
|
|
295
|
+
}
|
|
296
|
+
clientForQuery() {
|
|
297
|
+
if (!this.client) {
|
|
298
|
+
this.client = this.clientFactory.create(this.url);
|
|
299
|
+
}
|
|
300
|
+
return this.client;
|
|
301
|
+
}
|
|
302
|
+
assertConnection(connectionId) {
|
|
303
|
+
if (connectionId !== this.connectionId) {
|
|
304
|
+
throw new Error(`ktx MongoDB connector ${this.id} cannot serve connection ${connectionId}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { KtxDialect } from '../../context/connections/dialects.js';
|
|
2
|
+
import type { KtxDialectTableRef } from '../../context/connections/dialect-helpers.js';
|
|
3
|
+
import type { KtxSchemaDimensionType, KtxTableRef } from '../../context/scan/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Display/type-mapping half of the dialect contract for MongoDB. Collections map
|
|
6
|
+
* to `db.collection` display refs (ansi two-part shape). MongoDB is a non-SQL
|
|
7
|
+
* source, so it implements {@link KtxDialect} only — never {@link KtxSqlDialect}.
|
|
8
|
+
*/
|
|
9
|
+
export declare class KtxMongoDbDialect implements KtxDialect {
|
|
10
|
+
readonly type: "mongodb";
|
|
11
|
+
private readonly typeMappings;
|
|
12
|
+
formatDisplayRef(table: KtxDialectTableRef): string;
|
|
13
|
+
parseDisplayRef(display: string): KtxTableRef | null;
|
|
14
|
+
columnDisplayTablePartCount(): 1 | 2 | 3;
|
|
15
|
+
mapDataType(nativeType: string): string;
|
|
16
|
+
mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { columnDisplayPartCount, formatDialectDisplayRef, parseDialectDisplayRef, } from '../../context/connections/dialect-helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Display/type-mapping half of the dialect contract for MongoDB. Collections map
|
|
4
|
+
* to `db.collection` display refs (ansi two-part shape). MongoDB is a non-SQL
|
|
5
|
+
* source, so it implements {@link KtxDialect} only — never {@link KtxSqlDialect}.
|
|
6
|
+
*/
|
|
7
|
+
export class KtxMongoDbDialect {
|
|
8
|
+
type = 'mongodb';
|
|
9
|
+
typeMappings = {
|
|
10
|
+
objectid: 'string',
|
|
11
|
+
string: 'string',
|
|
12
|
+
uuid: 'string',
|
|
13
|
+
binary: 'string',
|
|
14
|
+
regex: 'string',
|
|
15
|
+
int: 'number',
|
|
16
|
+
long: 'number',
|
|
17
|
+
double: 'number',
|
|
18
|
+
decimal: 'number',
|
|
19
|
+
bool: 'boolean',
|
|
20
|
+
date: 'time',
|
|
21
|
+
timestamp: 'time',
|
|
22
|
+
object: 'string',
|
|
23
|
+
array: 'string',
|
|
24
|
+
json: 'string',
|
|
25
|
+
null: 'string',
|
|
26
|
+
mixed: 'string',
|
|
27
|
+
};
|
|
28
|
+
formatDisplayRef(table) {
|
|
29
|
+
return formatDialectDisplayRef(table, 'ansi');
|
|
30
|
+
}
|
|
31
|
+
parseDisplayRef(display) {
|
|
32
|
+
return parseDialectDisplayRef(display, 'ansi');
|
|
33
|
+
}
|
|
34
|
+
columnDisplayTablePartCount() {
|
|
35
|
+
return columnDisplayPartCount('ansi');
|
|
36
|
+
}
|
|
37
|
+
mapDataType(nativeType) {
|
|
38
|
+
const normalized = nativeType.toLowerCase().trim();
|
|
39
|
+
if (normalized === 'object' || normalized === 'array') {
|
|
40
|
+
return 'json';
|
|
41
|
+
}
|
|
42
|
+
return normalized || 'mixed';
|
|
43
|
+
}
|
|
44
|
+
mapToDimensionType(nativeType) {
|
|
45
|
+
if (!nativeType) {
|
|
46
|
+
return 'string';
|
|
47
|
+
}
|
|
48
|
+
return this.typeMappings[nativeType.toLowerCase().trim()] ?? 'string';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LiveDatabaseIntrospectionPort } from '../../context/ingest/adapters/live-database/types.js';
|
|
2
|
+
import type { KtxProjectConnectionConfig } from '../../context/project/config.js';
|
|
3
|
+
import { type KtxMongoClientFactory } from './connector.js';
|
|
4
|
+
interface CreateMongoDbLiveDatabaseIntrospectionOptions {
|
|
5
|
+
connections: Record<string, KtxProjectConnectionConfig>;
|
|
6
|
+
clientFactory?: KtxMongoClientFactory;
|
|
7
|
+
now?: () => Date;
|
|
8
|
+
}
|
|
9
|
+
export declare function createMongoDbLiveDatabaseIntrospection(options: CreateMongoDbLiveDatabaseIntrospectionOptions): LiveDatabaseIntrospectionPort;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { KtxMongoDbScanConnector, } from './connector.js';
|
|
2
|
+
export function createMongoDbLiveDatabaseIntrospection(options) {
|
|
3
|
+
return {
|
|
4
|
+
async extractSchema(connectionId, introspectionOptions) {
|
|
5
|
+
const connection = options.connections[connectionId];
|
|
6
|
+
const connector = new KtxMongoDbScanConnector({
|
|
7
|
+
connectionId,
|
|
8
|
+
connection,
|
|
9
|
+
clientFactory: options.clientFactory,
|
|
10
|
+
now: options.now,
|
|
11
|
+
});
|
|
12
|
+
try {
|
|
13
|
+
return await connector.introspect({
|
|
14
|
+
connectionId,
|
|
15
|
+
driver: 'mongodb',
|
|
16
|
+
...(introspectionOptions?.tableScope ? { tableScope: introspectionOptions.tableScope } : {}),
|
|
17
|
+
}, { runId: `mongodb-${connectionId}` });
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
await connector.cleanup();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KtxSchemaColumn } from '../../context/scan/types.js';
|
|
2
|
+
import type { KtxDialect } from '../../context/connections/dialects.js';
|
|
3
|
+
export type KtxMongoDocument = Record<string, unknown>;
|
|
4
|
+
/** Top-level field name MongoDB guarantees on every document; used as the primary key. */
|
|
5
|
+
export declare const MONGO_ID_FIELD = "_id";
|
|
6
|
+
/**
|
|
7
|
+
* Canonical BSON type name for a sampled value as the `mongodb` driver hydrates
|
|
8
|
+
* it: BSON wrapper objects expose `_bsontype`; everything else maps from the JS
|
|
9
|
+
* runtime type. Sub-documents and arrays collapse to opaque `object`/`array`.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare function bsonTypeOf(value: unknown): string;
|
|
13
|
+
/**
|
|
14
|
+
* Infer a flat column schema from sampled documents. Each top-level field becomes
|
|
15
|
+
* one column: BSON types are unioned (a field seen with >1 type is `mixed` and
|
|
16
|
+
* treated as a string), nullability is derived from field presence and observed
|
|
17
|
+
* nulls, and sub-documents/arrays remain a single opaque `json` column. `_id` is
|
|
18
|
+
* the non-nullable primary key.
|
|
19
|
+
*/
|
|
20
|
+
export declare function inferKtxMongoCollectionColumns(documents: readonly KtxMongoDocument[], dialect: KtxDialect): KtxSchemaColumn[];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Top-level field name MongoDB guarantees on every document; used as the primary key. */
|
|
2
|
+
export const MONGO_ID_FIELD = '_id';
|
|
3
|
+
const BSON_TYPE_NAMES = {
|
|
4
|
+
objectid: 'objectId',
|
|
5
|
+
int32: 'int',
|
|
6
|
+
long: 'long',
|
|
7
|
+
double: 'double',
|
|
8
|
+
decimal128: 'decimal',
|
|
9
|
+
binary: 'binary',
|
|
10
|
+
uuid: 'uuid',
|
|
11
|
+
timestamp: 'timestamp',
|
|
12
|
+
bsonregexp: 'regex',
|
|
13
|
+
bsonsymbol: 'string',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Canonical BSON type name for a sampled value as the `mongodb` driver hydrates
|
|
17
|
+
* it: BSON wrapper objects expose `_bsontype`; everything else maps from the JS
|
|
18
|
+
* runtime type. Sub-documents and arrays collapse to opaque `object`/`array`.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export function bsonTypeOf(value) {
|
|
22
|
+
if (value === null || value === undefined) {
|
|
23
|
+
return 'null';
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
return 'string';
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === 'boolean') {
|
|
29
|
+
return 'bool';
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'number') {
|
|
32
|
+
return Number.isInteger(value) ? 'int' : 'double';
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === 'bigint') {
|
|
35
|
+
return 'long';
|
|
36
|
+
}
|
|
37
|
+
if (value instanceof Date) {
|
|
38
|
+
return 'date';
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return 'array';
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'object') {
|
|
44
|
+
const bsontype = value._bsontype;
|
|
45
|
+
if (typeof bsontype === 'string') {
|
|
46
|
+
return BSON_TYPE_NAMES[bsontype.toLowerCase()] ?? bsontype;
|
|
47
|
+
}
|
|
48
|
+
return 'object';
|
|
49
|
+
}
|
|
50
|
+
return 'mixed';
|
|
51
|
+
}
|
|
52
|
+
function resolveNativeType(types) {
|
|
53
|
+
if (types.size === 0) {
|
|
54
|
+
return 'null';
|
|
55
|
+
}
|
|
56
|
+
if (types.size > 1) {
|
|
57
|
+
return 'mixed';
|
|
58
|
+
}
|
|
59
|
+
return [...types][0];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Infer a flat column schema from sampled documents. Each top-level field becomes
|
|
63
|
+
* one column: BSON types are unioned (a field seen with >1 type is `mixed` and
|
|
64
|
+
* treated as a string), nullability is derived from field presence and observed
|
|
65
|
+
* nulls, and sub-documents/arrays remain a single opaque `json` column. `_id` is
|
|
66
|
+
* the non-nullable primary key.
|
|
67
|
+
*/
|
|
68
|
+
export function inferKtxMongoCollectionColumns(documents, dialect) {
|
|
69
|
+
const total = documents.length;
|
|
70
|
+
const order = [];
|
|
71
|
+
const fields = new Map();
|
|
72
|
+
for (const document of documents) {
|
|
73
|
+
if (!document || typeof document !== 'object') {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const [name, value] of Object.entries(document)) {
|
|
77
|
+
let accumulator = fields.get(name);
|
|
78
|
+
if (!accumulator) {
|
|
79
|
+
accumulator = { types: new Set(), present: 0, nullSeen: false };
|
|
80
|
+
fields.set(name, accumulator);
|
|
81
|
+
order.push(name);
|
|
82
|
+
}
|
|
83
|
+
accumulator.present += 1;
|
|
84
|
+
const bsonType = bsonTypeOf(value);
|
|
85
|
+
if (bsonType === 'null') {
|
|
86
|
+
accumulator.nullSeen = true;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
accumulator.types.add(bsonType);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return order.map((name) => {
|
|
94
|
+
const accumulator = fields.get(name);
|
|
95
|
+
const nativeType = resolveNativeType(accumulator.types);
|
|
96
|
+
const isId = name === MONGO_ID_FIELD;
|
|
97
|
+
const nullable = isId
|
|
98
|
+
? false
|
|
99
|
+
: accumulator.present < total || accumulator.nullSeen || accumulator.types.size === 0;
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
nativeType,
|
|
103
|
+
normalizedType: dialect.mapDataType(nativeType),
|
|
104
|
+
dimensionType: dialect.mapToDimensionType(nativeType),
|
|
105
|
+
nullable,
|
|
106
|
+
primaryKey: isId,
|
|
107
|
+
comment: null,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -96,6 +96,7 @@ export declare class KtxMysqlScanConnector implements KtxScanConnector {
|
|
|
96
96
|
private readonly poolFactory;
|
|
97
97
|
private readonly endpointResolver?;
|
|
98
98
|
private readonly now;
|
|
99
|
+
private readonly deadlineMs;
|
|
99
100
|
private readonly dialect;
|
|
100
101
|
private pool;
|
|
101
102
|
private resolvedEndpoint;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import mysql from 'mysql2/promise';
|
|
2
|
-
import {
|
|
2
|
+
import { getSqlDialectForDriver } from '../../context/connections/dialects.js';
|
|
3
|
+
import { resolveQueryDeadlineMs, queryDeadlineExceededError } from '../../context/connections/query-deadline.js';
|
|
3
4
|
import { resolveStringReference } from '../shared/string-reference.js';
|
|
4
5
|
import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
|
|
5
6
|
import { constraintDiscoveryWarning, tryConstraintQuery, } from '../../context/scan/constraint-discovery.js';
|
|
@@ -96,6 +97,10 @@ function isDeniedError(error) {
|
|
|
96
97
|
code === 'ER_SPECIFIC_ACCESS_DENIED_ERROR' ||
|
|
97
98
|
code === 'ER_DBACCESS_DENIED_ERROR');
|
|
98
99
|
}
|
|
100
|
+
// errno 3024 = ER_QUERY_TIMEOUT, raised when max_execution_time is exceeded.
|
|
101
|
+
function isMysqlTimeoutError(error) {
|
|
102
|
+
return Boolean(error) && typeof error === 'object' && error.errno === 3024;
|
|
103
|
+
}
|
|
99
104
|
function pushConstraintWarnings(warnings, schemas, kind) {
|
|
100
105
|
for (const schema of schemas) {
|
|
101
106
|
warnings.push(constraintDiscoveryWarning({ schema, kind }));
|
|
@@ -183,7 +188,8 @@ export class KtxMysqlScanConnector {
|
|
|
183
188
|
poolFactory;
|
|
184
189
|
endpointResolver;
|
|
185
190
|
now;
|
|
186
|
-
|
|
191
|
+
deadlineMs;
|
|
192
|
+
dialect = getSqlDialectForDriver('mysql');
|
|
187
193
|
pool = null;
|
|
188
194
|
resolvedEndpoint = null;
|
|
189
195
|
constructor(options) {
|
|
@@ -197,6 +203,7 @@ export class KtxMysqlScanConnector {
|
|
|
197
203
|
this.poolFactory = options.poolFactory ?? new DefaultMysqlPoolFactory();
|
|
198
204
|
this.endpointResolver = options.endpointResolver;
|
|
199
205
|
this.now = options.now ?? (() => new Date());
|
|
206
|
+
this.deadlineMs = resolveQueryDeadlineMs(this.connection);
|
|
200
207
|
this.id = `mysql:${options.connectionId}`;
|
|
201
208
|
}
|
|
202
209
|
async testConnection() {
|
|
@@ -478,6 +485,9 @@ export class KtxMysqlScanConnector {
|
|
|
478
485
|
const pool = await this.poolForQuery();
|
|
479
486
|
const connection = await pool.getConnection();
|
|
480
487
|
try {
|
|
488
|
+
// max_execution_time (ms) bounds read-only SELECTs server-side; our path
|
|
489
|
+
// only runs SELECT/WITH, so the session setting always applies.
|
|
490
|
+
await connection.query('SET SESSION max_execution_time = ?', [this.deadlineMs]);
|
|
481
491
|
const [rows, fields] = await connection.query(assertReadOnlySql(sql), queryParams(params));
|
|
482
492
|
const headers = fields.map((field) => field.name);
|
|
483
493
|
const headerTypes = fields.map((field) => String(field.type ?? 'unknown'));
|
|
@@ -488,6 +498,12 @@ export class KtxMysqlScanConnector {
|
|
|
488
498
|
totalRows: rows.length,
|
|
489
499
|
};
|
|
490
500
|
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
if (isMysqlTimeoutError(error)) {
|
|
503
|
+
throw queryDeadlineExceededError(this.deadlineMs, { cause: error });
|
|
504
|
+
}
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
491
507
|
finally {
|
|
492
508
|
connection.release();
|
|
493
509
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KtxSqlDialect } from '../../context/connections/dialects.js';
|
|
2
2
|
import type { KtxSchemaDimensionType, KtxTableRef } from '../../context/scan/types.js';
|
|
3
3
|
type MysqlTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
|
|
4
4
|
/** @internal */
|
|
5
|
-
export declare class KtxMysqlDialect implements
|
|
5
|
+
export declare class KtxMysqlDialect implements KtxSqlDialect {
|
|
6
6
|
readonly type: "mysql";
|
|
7
7
|
private readonly typeMappings;
|
|
8
8
|
quoteIdentifier(identifier: string): string;
|
|
@@ -112,6 +112,7 @@ export declare class KtxPostgresScanConnector implements KtxScanConnector {
|
|
|
112
112
|
private readonly poolFactory;
|
|
113
113
|
private readonly endpointResolver?;
|
|
114
114
|
private readonly now;
|
|
115
|
+
private readonly deadlineMs;
|
|
115
116
|
private readonly dialect;
|
|
116
117
|
private pool;
|
|
117
118
|
private lastIdlePoolError;
|