@kaelio/ktx 0.14.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 +2 -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/ingest/adapters/sigma/chunk.d.ts +6 -0
- package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
- package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
- package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
- package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
- package/dist/context/ingest/adapters/sigma/client.js +176 -0
- package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/sigma/detect.js +23 -0
- package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
- package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
- package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
- package/dist/context/ingest/adapters/sigma/project.js +186 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
- package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
- package/dist/context/ingest/adapters/sigma/types.js +82 -0
- package/dist/context/ingest/local-adapters.d.ts +2 -1
- package/dist/context/ingest/local-adapters.js +22 -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 +122 -0
- package/dist/context/project/driver-schemas.d.ts +61 -0
- package/dist/context/project/driver-schemas.js +47 -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/public-ingest.js +1 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +52 -2
- package/dist/setup-sources.d.ts +2 -1
- package/dist/setup-sources.js +84 -0
- package/dist/skills/sigma_ingest/SKILL.md +189 -0
- 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.14.0-py3-none-any.whl +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { KtxTableRefKey } from './table-ref.js';
|
|
2
|
-
export type KtxConnectionDriver = 'sqlite' | 'postgres' | 'sqlserver' | 'bigquery' | 'snowflake' | 'mysql' | 'clickhouse' | 'mongodb';
|
|
2
|
+
export type KtxConnectionDriver = 'sqlite' | 'duckdb' | 'postgres' | 'sqlserver' | 'bigquery' | 'snowflake' | 'mysql' | 'clickhouse' | 'athena' | 'mongodb';
|
|
3
3
|
/** Canonical scan-mode registry. Runtime validation derives its allowlist here. */
|
|
4
4
|
export declare const KTX_SCAN_MODES: readonly ["structural", "relationships", "enriched"];
|
|
5
5
|
export type KtxScanMode = (typeof KTX_SCAN_MODES)[number];
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { isSqlQueryableDriver } from '../connections/dialects.js';
|
|
2
2
|
import { FEDERATED_CONNECTION_ID } from '../connections/federation.js';
|
|
3
|
+
import { connectionQueryPolicy, restrictedFederatedMemberIds } from '../connections/query-policy.js';
|
|
3
4
|
import { resolveRequiredConnectionId } from '../connections/resolve-connection.js';
|
|
4
5
|
import { sqlAnalysisDialectForDriver } from '../sql-analysis/dialect.js';
|
|
5
6
|
import { loadLocalSlSourceRecords } from './local-sl.js';
|
|
6
7
|
import { toResolvedWire } from './semantic-layer.service.js';
|
|
7
8
|
import { assertSafeConnectionId } from './source-files.js';
|
|
8
9
|
const COMPILE_ONLY_REASON = 'Local semantic-layer query compiled SQL but no data-source execution adapter is configured.';
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const FEDERATED_SL_QUERY_PREFIX = `Semantic-layer queries are per-connection and cannot target the federated connection '${FEDERATED_CONNECTION_ID}'. `;
|
|
11
|
+
// The raw-SQL fallback is only valid when federated raw SQL is allowed; when a
|
|
12
|
+
// member is restricted (query_policy: semantic-layer-only), assertRawSqlAllowed
|
|
13
|
+
// rejects the same path, so directing the agent there would burn a guaranteed
|
|
14
|
+
// failure. Derive the message from the restricted-member set instead.
|
|
15
|
+
function federatedSlQueryUnsupportedMessage(project) {
|
|
16
|
+
const restricted = restrictedFederatedMemberIds(project.config, project.projectDir);
|
|
17
|
+
if (restricted.length > 0) {
|
|
18
|
+
return (FEDERATED_SL_QUERY_PREFIX +
|
|
19
|
+
`Cross-database SQL through '${FEDERATED_CONNECTION_ID}' is also disabled because member connection(s) ` +
|
|
20
|
+
`${restricted.map((id) => `'${id}'`).join(', ')} are restricted to semantic-layer queries ` +
|
|
21
|
+
'(query_policy: semantic-layer-only). Query each connection on its own through the semantic layer ' +
|
|
22
|
+
'(the sl_query tool or `ktx sl query` with its connection id).');
|
|
23
|
+
}
|
|
24
|
+
return (FEDERATED_SL_QUERY_PREFIX +
|
|
25
|
+
`Run a cross-database query as read-only SQL instead — ktx sql -c ${FEDERATED_CONNECTION_ID} "SELECT ..." or the sql_execution tool — ` +
|
|
26
|
+
'using catalog-qualified table names (connectionId.schema.table, or connectionId.table for sqlite; ' +
|
|
27
|
+
'double-quote ids that are not bare identifiers, e.g. "books-db".public.books).');
|
|
28
|
+
}
|
|
13
29
|
function resolveLocalConnectionId(project, requested) {
|
|
14
30
|
return assertSafeConnectionId(resolveRequiredConnectionId(project.config, requested));
|
|
15
31
|
}
|
|
@@ -39,7 +55,7 @@ function headersFromColumns(columns) {
|
|
|
39
55
|
}
|
|
40
56
|
export async function compileLocalSlQuery(project, options) {
|
|
41
57
|
if (options.connectionId === FEDERATED_CONNECTION_ID) {
|
|
42
|
-
throw new Error(
|
|
58
|
+
throw new Error(federatedSlQueryUnsupportedMessage(project));
|
|
43
59
|
}
|
|
44
60
|
await options.onProgress?.({ progress: 0, message: 'Compiling query' });
|
|
45
61
|
const connectionId = resolveLocalConnectionId(project, options.connectionId);
|
|
@@ -50,11 +66,12 @@ export async function compileLocalSlQuery(project, options) {
|
|
|
50
66
|
}
|
|
51
67
|
const dialect = sqlAnalysisDialectForDriver(driver);
|
|
52
68
|
const sources = await loadComputableSources(project, connectionId);
|
|
69
|
+
const predefinedMeasuresOnly = connectionQueryPolicy(project.config.connections[connectionId]) === 'semantic-layer-only';
|
|
53
70
|
await options.onProgress?.({ progress: 0.3, message: 'Generating SQL' });
|
|
54
71
|
const response = await options.compute.query({
|
|
55
72
|
sources,
|
|
56
73
|
dialect,
|
|
57
|
-
query: options.query,
|
|
74
|
+
query: { ...options.query, predefined_measures_only: predefinedMeasuresOnly },
|
|
58
75
|
});
|
|
59
76
|
if (!options.execute) {
|
|
60
77
|
await options.onProgress?.({ progress: 1, message: 'Fetched 0 rows' });
|
|
@@ -147,10 +147,6 @@ export declare class SemanticLayerService {
|
|
|
147
147
|
descriptions: Record<string, string>;
|
|
148
148
|
}>;
|
|
149
149
|
}>;
|
|
150
|
-
/**
|
|
151
|
-
* All callers should use this instead of maintaining their own dialect maps.
|
|
152
|
-
*/
|
|
153
|
-
static mapDialect(connectionType: string): string;
|
|
154
150
|
/**
|
|
155
151
|
* Execute a semantic layer query: load composed sources, generate SQL via
|
|
156
152
|
* the python SL engine, and execute the generated SQL against the data source.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
import { noopLogger } from '../../context/core/config.js';
|
|
3
|
+
import { dialectForConnectionType } from '../connections/connection-type-dialect.js';
|
|
3
4
|
import { normalizeSemanticLayerDescriptions } from './description-normalization.js';
|
|
4
5
|
import { isOverlaySource, resolvedSourceSchema, sourceDefinitionSchema, sourceOverlaySchema } from './schemas.js';
|
|
5
6
|
import { isSlYamlPath, resolveSlSourceFile, slDeclaredSourceName, slSourceFilePath } from './source-files.js';
|
|
@@ -572,7 +573,7 @@ export class SemanticLayerService {
|
|
|
572
573
|
if (!connection) {
|
|
573
574
|
throw new Error(`Data source not found: ${connectionId}`);
|
|
574
575
|
}
|
|
575
|
-
return
|
|
576
|
+
return dialectForConnectionType(connection.connectionType);
|
|
576
577
|
}
|
|
577
578
|
async listFilesForConnection(connectionId) {
|
|
578
579
|
const dir = `${SL_DIR_PREFIX}/${connectionId}`;
|
|
@@ -937,24 +938,6 @@ export class SemanticLayerService {
|
|
|
937
938
|
}
|
|
938
939
|
return { columns, tables };
|
|
939
940
|
}
|
|
940
|
-
/**
|
|
941
|
-
* All callers should use this instead of maintaining their own dialect maps.
|
|
942
|
-
*/
|
|
943
|
-
static mapDialect(connectionType) {
|
|
944
|
-
const normalized = connectionType.toUpperCase();
|
|
945
|
-
const map = {
|
|
946
|
-
POSTGRES: 'postgres',
|
|
947
|
-
BIGQUERY: 'bigquery',
|
|
948
|
-
SNOWFLAKE: 'snowflake',
|
|
949
|
-
MYSQL: 'mysql',
|
|
950
|
-
SQLSERVER: 'tsql',
|
|
951
|
-
SQLITE: 'sqlite',
|
|
952
|
-
DUCKDB: 'duckdb',
|
|
953
|
-
CLICKHOUSE: 'clickhouse',
|
|
954
|
-
DATABRICKS: 'databricks',
|
|
955
|
-
};
|
|
956
|
-
return map[normalized] ?? 'postgres';
|
|
957
|
-
}
|
|
958
941
|
/**
|
|
959
942
|
* Execute a semantic layer query: load composed sources, generate SQL via
|
|
960
943
|
* the python SL engine, and execute the generated SQL against the data source.
|
|
@@ -980,7 +963,7 @@ export class SemanticLayerService {
|
|
|
980
963
|
if (!connection) {
|
|
981
964
|
throw new Error(`Data source not found: ${connectionId}`);
|
|
982
965
|
}
|
|
983
|
-
const dialect =
|
|
966
|
+
const dialect = dialectForConnectionType(connection.connectionType);
|
|
984
967
|
// 3. Generate SQL via python SL engine
|
|
985
968
|
const { data: slResult, error: slError } = await this.python.query({
|
|
986
969
|
sources: sources.map(toResolvedWire),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GitService } from '../../../context/core/git.service.js';
|
|
2
2
|
import type { KtxFileStorePort } from '../../../context/core/file-store.js';
|
|
3
3
|
import type { SlConnectionCatalogPort, SlSourcesIndexPort } from '../ports.js';
|
|
4
|
-
import { SemanticLayerService } from '../semantic-layer.service.js';
|
|
4
|
+
import type { SemanticLayerService } from '../semantic-layer.service.js';
|
|
5
5
|
export interface SlValidationDeps {
|
|
6
6
|
semanticLayerService: SemanticLayerService;
|
|
7
7
|
connections: SlConnectionCatalogPort;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
import { SYSTEM_GIT_AUTHOR } from '../../../context/tools/authors.js';
|
|
3
|
+
import { dialectForConnectionType } from '../../connections/connection-type-dialect.js';
|
|
3
4
|
import { sourceOverlaySchema } from '../schemas.js';
|
|
4
|
-
import { SemanticLayerService } from '../semantic-layer.service.js';
|
|
5
5
|
import { resolveSlSourceFile, slSourceFilePath } from '../source-files.js';
|
|
6
6
|
import { sourceDefinitionSchema } from './base-semantic-layer.tool.js';
|
|
7
7
|
function resolveDialect(warehouse) {
|
|
8
8
|
if (!warehouse) {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
-
return
|
|
11
|
+
return dialectForConnectionType(warehouse);
|
|
12
12
|
}
|
|
13
13
|
function wrapWithZeroRowQuery(sql, dialect) {
|
|
14
14
|
if (dialect === 'tsql') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SqlAnalysisDialect } from './ports.js';
|
|
2
2
|
/** @internal Dialects with an authored ./dialects/<dialect>.md file. */
|
|
3
|
-
export declare const DIALECTS_WITH_NOTES: readonly ["postgres", "mysql", "snowflake", "bigquery", "sqlite", "clickhouse", "tsql"];
|
|
3
|
+
export declare const DIALECTS_WITH_NOTES: readonly ["postgres", "mysql", "snowflake", "bigquery", "sqlite", "duckdb", "clickhouse", "tsql", "athena"];
|
|
4
4
|
/**
|
|
5
5
|
* SQL syntax notes for a resolved dialect. Falls back to `postgres` — the
|
|
6
6
|
* resolver's own default for unrecognized drivers — so any SQL connection yields
|
|
@@ -4,8 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
// dialect), served by the sql_dialect_notes MCP tool. They are package-internal:
|
|
5
5
|
// copy-runtime-assets.mjs ships them to dist, and they are never installed onto an
|
|
6
6
|
// agent target. The set covers every dialect reachable from a configured warehouse
|
|
7
|
-
// driver;
|
|
8
|
-
// them.
|
|
7
|
+
// driver; databricks is intentionally absent because no connector produces it.
|
|
9
8
|
/** @internal Dialects with an authored ./dialects/<dialect>.md file. */
|
|
10
9
|
export const DIALECTS_WITH_NOTES = [
|
|
11
10
|
'postgres',
|
|
@@ -13,8 +12,10 @@ export const DIALECTS_WITH_NOTES = [
|
|
|
13
12
|
'snowflake',
|
|
14
13
|
'bigquery',
|
|
15
14
|
'sqlite',
|
|
15
|
+
'duckdb',
|
|
16
16
|
'clickhouse',
|
|
17
17
|
'tsql',
|
|
18
|
+
'athena',
|
|
18
19
|
];
|
|
19
20
|
const notesCache = new Map();
|
|
20
21
|
function readDialectNotes(dialect) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
**athena** SQL conventions (Trino engine over the Glue Data Catalog):
|
|
2
|
+
- **FQTN:** `database.table` (e.g. `analytics.orders`); a bare `table` resolves against the query's default database. Cross-catalog is `catalog.database.table` (e.g. `awsdatacatalog.analytics.orders`).
|
|
3
|
+
- **Identifiers:** case-insensitive and folded to lowercase; double-quote (`"Name"`) to keep case, spaces, or a reserved word. String literals use single quotes only.
|
|
4
|
+
- **Date/time:** native `DATE`/`TIMESTAMP`. Bucket with `date_trunc('month', ts)`, pull parts with `EXTRACT(YEAR FROM ts)`, shift with `date_add('day', -30, current_date)`, difference with `date_diff('day', a, b)`, and format with `date_format(ts, '%Y-%m')`. Parse text with `date_parse(str, '%Y-%m-%d')` or `from_iso8601_timestamp(str)`; `current_date` / `now()` are available.
|
|
5
|
+
- **Top-N / windows:** Athena has no `QUALIFY` — wrap the window in a subquery and filter it: `SELECT * FROM (SELECT ..., ROW_NUMBER() OVER (PARTITION BY key ORDER BY x DESC) AS rn FROM t) WHERE rn = 1` returns one row per key. Use `ORDER BY ... LIMIT n` for a global top-N, and paginate with `OFFSET m LIMIT n` (offset first — `LIMIT n OFFSET m` is a syntax error).
|
|
6
|
+
- **Series:** `CROSS JOIN UNNEST(sequence(DATE '2023-01-01', DATE '2023-12-01', INTERVAL '1' MONTH)) AS s(d)` expands a generated array into a date spine (use `sequence(1, 12)` for integers), then `LEFT JOIN` the aggregated facts onto it so empty periods still appear.
|
|
7
|
+
- **Rolling window over time:** a native `RANGE` frame spans real dates and tolerates gaps — `AVG(amount) OVER (ORDER BY day RANGE BETWEEN INTERVAL '29' DAY PRECEDING AND CURRENT ROW)` is a trailing 30-day average without a spine; guard minimum periods with `COUNT(*) OVER (<same frame>)`.
|
|
8
|
+
- **Approximate aggregates:** `approx_distinct(x)` for cardinality and `approx_percentile(x, 0.5)` for quantiles are far cheaper than exact `COUNT(DISTINCT ...)` on large scans.
|
|
9
|
+
- **Arrays & maps:** explode with `CROSS JOIN UNNEST(arr) AS t(x)` (add `WITH ORDINALITY` for an index); build with `array_agg(x)`, join with `array_join(arr, ',')`, index 1-based (`arr[1]`), and read a map with `element_at(m, key)`.
|
|
10
|
+
- **Safe cast:** `TRY_CAST(x AS DOUBLE)` yields `NULL` for a value that does not parse instead of raising, so counting residual `NULL`s catches an encoding the sample missed; `TRY(expr)` swallows other runtime errors.
|
|
11
|
+
- **Integer division:** `/` between integers truncates (`5 / 2` → `2`); cast an operand (`x / CAST(y AS DOUBLE)`) to keep the fraction, and round only in the final projection.
|
|
12
|
+
- **JSON:** `json_extract_scalar(col, '$.a.b')` returns varchar, `json_extract(col, '$.a')` returns json; cast a JSON string with `CAST(json_parse(col) AS ...)`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
**duckdb** SQL conventions:
|
|
2
|
+
- **FQTN:** `schema.table` within one database (e.g. `main.orders`); a bare `table` resolves against `main`. A second, attached database is `db.schema.table` (e.g. `sales.main.orders`).
|
|
3
|
+
- **Identifiers:** case-insensitive; double-quote (`"Name"`) to keep a name with spaces or a reserved word.
|
|
4
|
+
- **Date/time:** native `DATE`/`TIMESTAMP`. Bucket with `date_trunc('month', ts)`, pull parts with `EXTRACT(YEAR FROM ts)`, format with `strftime(ts, '%Y-%m')`, and use `CURRENT_DATE`; cast text with `col::DATE` (or `TRY_CAST(col AS DATE)` to null bad values).
|
|
5
|
+
- **Series:** `FROM generate_series(DATE '2023-01-01', DATE '2023-12-01', INTERVAL 1 MONTH) AS s(d)` builds a date spine (use `range(...)` for integers), then `LEFT JOIN` the aggregated facts onto it so empty periods still appear.
|
|
6
|
+
- **Rolling window over time:** a native calendar-range frame spans real dates and tolerates gaps — `AVG(amount) OVER (ORDER BY day RANGE BETWEEN INTERVAL 29 DAYS PRECEDING AND CURRENT ROW)` is a trailing 30-day average without a spine; guard minimum periods with `COUNT(*) OVER (<same frame>)`.
|
|
7
|
+
- **Integer division:** unlike postgres, `/` is true division (`5 / 2` → `2.5`), so a ratio keeps its fraction; use `//` for floor division (`5 // 2` → `2`) when you want the integer quotient, and round only in the final projection.
|
|
8
|
+
- **Safe cast:** duckdb has `TRY_CAST` — `TRY_CAST(x AS DOUBLE)` yields `NULL` for a value that does not parse instead of raising, so counting residual `NULL`s among non-sentinel rows catches an encoding the sample missed without a regex guard.
|
|
9
|
+
- **Top-N / windows:** filter a window inline with `QUALIFY` — `SELECT ... QUALIFY ROW_NUMBER() OVER (PARTITION BY key ORDER BY x DESC) = 1` returns one row per key without a wrapping CTE; use `ORDER BY ... LIMIT n` for a global top-N.
|
|
10
|
+
- **JSON / semi-structured:** `col->'k'` returns JSON, `col->>'k'` returns text, deep path `json_extract(col, '$.a.b')`; duckdb also has native `STRUCT`, `LIST`, and `MAP` — read a struct field with `col.field` and a list element with `col[1]` (1-indexed).
|
package/dist/local-adapters.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createAthenaLiveDatabaseIntrospection } from './connectors/athena/live-database-introspection.js';
|
|
2
|
+
import { isKtxAthenaConnectionConfig } from './connectors/athena/connector.js';
|
|
1
3
|
import { createBigQueryLiveDatabaseIntrospection } from './connectors/bigquery/live-database-introspection.js';
|
|
2
4
|
import { isKtxBigQueryConnectionConfig, KtxBigQueryScanConnector } from './connectors/bigquery/connector.js';
|
|
3
5
|
import { createClickHouseLiveDatabaseIntrospection } from './connectors/clickhouse/live-database-introspection.js';
|
|
@@ -9,6 +11,8 @@ import { isKtxPostgresConnectionConfig } from './connectors/postgres/connector.j
|
|
|
9
11
|
import { KtxPostgresHistoricSqlQueryClient } from './connectors/postgres/historic-sql-query-client.js';
|
|
10
12
|
import { createSqliteLiveDatabaseIntrospection } from './connectors/sqlite/live-database-introspection.js';
|
|
11
13
|
import { isKtxSqliteConnectionConfig } from './connectors/sqlite/connector.js';
|
|
14
|
+
import { createDuckDbLiveDatabaseIntrospection } from './connectors/duckdb/live-database-introspection.js';
|
|
15
|
+
import { isKtxDuckDbConnectionConfig } from './connectors/duckdb/connector.js';
|
|
12
16
|
import { createSqlServerLiveDatabaseIntrospection } from './connectors/sqlserver/live-database-introspection.js';
|
|
13
17
|
import { isKtxSqlServerConnectionConfig } from './connectors/sqlserver/connector.js';
|
|
14
18
|
import { BigQueryHistoricSqlQueryHistoryReader } from './context/ingest/adapters/historic-sql/bigquery-query-history-reader.js';
|
|
@@ -74,6 +78,10 @@ function createKtxCliLiveDatabaseIntrospection(project, options = {}) {
|
|
|
74
78
|
projectDir: project.projectDir,
|
|
75
79
|
connections: project.config.connections,
|
|
76
80
|
});
|
|
81
|
+
const duckdb = createDuckDbLiveDatabaseIntrospection({
|
|
82
|
+
projectDir: project.projectDir,
|
|
83
|
+
connections: project.config.connections,
|
|
84
|
+
});
|
|
77
85
|
const mysql = createMysqlLiveDatabaseIntrospection({
|
|
78
86
|
connections: project.config.connections,
|
|
79
87
|
});
|
|
@@ -89,6 +97,9 @@ function createKtxCliLiveDatabaseIntrospection(project, options = {}) {
|
|
|
89
97
|
const bigquery = createBigQueryLiveDatabaseIntrospection({
|
|
90
98
|
connections: project.config.connections,
|
|
91
99
|
});
|
|
100
|
+
const athena = createAthenaLiveDatabaseIntrospection({
|
|
101
|
+
connections: project.config.connections,
|
|
102
|
+
});
|
|
92
103
|
return {
|
|
93
104
|
async extractSchema(connectionId, options) {
|
|
94
105
|
const connection = project.config.connections[connectionId];
|
|
@@ -109,6 +120,9 @@ function createKtxCliLiveDatabaseIntrospection(project, options = {}) {
|
|
|
109
120
|
if (isKtxSqliteConnectionConfig(connection)) {
|
|
110
121
|
return sqlite.extractSchema(connectionId, options);
|
|
111
122
|
}
|
|
123
|
+
if (isKtxDuckDbConnectionConfig(connection)) {
|
|
124
|
+
return duckdb.extractSchema(connectionId, options);
|
|
125
|
+
}
|
|
112
126
|
if (isKtxMysqlConnectionConfig(connection)) {
|
|
113
127
|
return mysql.extractSchema(connectionId, options);
|
|
114
128
|
}
|
|
@@ -121,6 +135,9 @@ function createKtxCliLiveDatabaseIntrospection(project, options = {}) {
|
|
|
121
135
|
if (isKtxBigQueryConnectionConfig(connection)) {
|
|
122
136
|
return bigquery.extractSchema(connectionId, options);
|
|
123
137
|
}
|
|
138
|
+
if (isKtxAthenaConnectionConfig(connection)) {
|
|
139
|
+
return athena.extractSchema(connectionId, options);
|
|
140
|
+
}
|
|
124
141
|
if (hasSnowflakeDriver(connection)) {
|
|
125
142
|
const { createSnowflakeLiveDatabaseIntrospection } = await import('./connectors/snowflake/live-database-introspection.js');
|
|
126
143
|
const { isKtxSnowflakeConnectionConfig } = await import('./connectors/snowflake/connector.js');
|
package/dist/public-ingest.js
CHANGED
|
@@ -9,7 +9,7 @@ import { type DatabaseScopePickResult, type PickDatabaseScopeArgs } from './data
|
|
|
9
9
|
import type { KtxManagedPythonInstallPolicy } from './managed-python-command.js';
|
|
10
10
|
import type { ManagedPythonCoreDaemonOptions } from './managed-python-http.js';
|
|
11
11
|
import { type KtxSetupPromptOption } from './setup-prompts.js';
|
|
12
|
-
export type KtxSetupDatabaseDriver = 'sqlite' | 'postgres' | 'mysql' | 'clickhouse' | 'sqlserver' | 'bigquery' | 'snowflake' | 'mongodb';
|
|
12
|
+
export type KtxSetupDatabaseDriver = 'sqlite' | 'duckdb' | 'postgres' | 'mysql' | 'clickhouse' | 'sqlserver' | 'bigquery' | 'snowflake' | 'athena' | 'mongodb';
|
|
13
13
|
export interface KtxSetupDatabasesArgs {
|
|
14
14
|
projectDir: string;
|
|
15
15
|
inputMode: 'auto' | 'disabled';
|
package/dist/setup-databases.js
CHANGED
|
@@ -37,8 +37,10 @@ const DRIVER_OPTIONS = [
|
|
|
37
37
|
{ value: 'mysql', label: 'MySQL' },
|
|
38
38
|
{ value: 'clickhouse', label: 'ClickHouse' },
|
|
39
39
|
{ value: 'sqlserver', label: 'SQL Server' },
|
|
40
|
+
{ value: 'athena', label: 'Amazon Athena' },
|
|
40
41
|
{ value: 'mongodb', label: 'MongoDB' },
|
|
41
42
|
{ value: 'sqlite', label: 'SQLite' },
|
|
43
|
+
{ value: 'duckdb', label: 'DuckDB' },
|
|
42
44
|
];
|
|
43
45
|
const DRIVER_LABELS = Object.fromEntries(DRIVER_OPTIONS.map((option) => [option.value, option.label]));
|
|
44
46
|
const HISTORIC_SQL_DIALECT_BY_DRIVER = {
|
|
@@ -48,12 +50,14 @@ const HISTORIC_SQL_DIALECT_BY_DRIVER = {
|
|
|
48
50
|
};
|
|
49
51
|
const DEFAULT_CONNECTION_IDS = {
|
|
50
52
|
sqlite: 'sqlite-local',
|
|
53
|
+
duckdb: 'duckdb-local',
|
|
51
54
|
postgres: 'postgres-warehouse',
|
|
52
55
|
mysql: 'mysql-warehouse',
|
|
53
56
|
clickhouse: 'clickhouse-warehouse',
|
|
54
57
|
sqlserver: 'sqlserver-warehouse',
|
|
55
58
|
bigquery: 'bigquery-warehouse',
|
|
56
59
|
snowflake: 'snowflake-warehouse',
|
|
60
|
+
athena: 'athena-warehouse',
|
|
57
61
|
mongodb: 'mongodb-source',
|
|
58
62
|
};
|
|
59
63
|
const SUGGESTED_SCOPE_PATTERN = /^(mart|prod|analytics|core|dim|fact|gold)(_|$)/i;
|
|
@@ -118,6 +122,13 @@ const SCOPE_DISCOVERY_SPECS = {
|
|
|
118
122
|
configSingleField: 'schema_name',
|
|
119
123
|
suggest: defaultSuggest,
|
|
120
124
|
},
|
|
125
|
+
athena: {
|
|
126
|
+
noun: 'database',
|
|
127
|
+
nounPlural: 'databases',
|
|
128
|
+
promptLabel: 'Glue databases',
|
|
129
|
+
configArrayField: 'databases',
|
|
130
|
+
suggest: defaultSuggest,
|
|
131
|
+
},
|
|
121
132
|
};
|
|
122
133
|
const DRIVER_CONNECTION_DEFAULTS = {
|
|
123
134
|
postgres: { port: '5432' },
|
|
@@ -541,6 +552,15 @@ async function buildConnectionConfig(input) {
|
|
|
541
552
|
return 'back';
|
|
542
553
|
return path ? { driver: 'sqlite', path } : null;
|
|
543
554
|
}
|
|
555
|
+
if (driver === 'duckdb') {
|
|
556
|
+
if (args.inputMode === 'disabled' && !args.databaseUrl)
|
|
557
|
+
return null;
|
|
558
|
+
const path = args.databaseUrl ??
|
|
559
|
+
(await promptText(prompts, 'DuckDB database file\nEnter a relative or absolute path, for example ./warehouse.duckdb.', stringConfigField(input.existingConnection, 'path')));
|
|
560
|
+
if (path === undefined)
|
|
561
|
+
return 'back';
|
|
562
|
+
return path ? { driver: 'duckdb', path } : null;
|
|
563
|
+
}
|
|
544
564
|
if (driver === 'postgres' || driver === 'mysql' || driver === 'clickhouse' || driver === 'sqlserver') {
|
|
545
565
|
return await buildUrlConnectionConfig({
|
|
546
566
|
driver,
|
|
@@ -664,6 +684,34 @@ async function buildConnectionConfig(input) {
|
|
|
664
684
|
...(role ? { role } : {}),
|
|
665
685
|
};
|
|
666
686
|
}
|
|
687
|
+
if (driver === 'athena') {
|
|
688
|
+
if (args.inputMode === 'disabled' && !args.databaseUrl)
|
|
689
|
+
return null;
|
|
690
|
+
const region = await promptText(prompts, 'AWS region\nFor example us-east-1.', stringConfigField(input.existingConnection, 'region'));
|
|
691
|
+
if (region === undefined)
|
|
692
|
+
return 'back';
|
|
693
|
+
if (!region)
|
|
694
|
+
return null;
|
|
695
|
+
const s3StagingDir = await promptText(prompts, 'S3 staging directory\nAthena writes query results here. For example s3://my-bucket/athena-results/.', stringConfigField(input.existingConnection, 's3_staging_dir'));
|
|
696
|
+
if (s3StagingDir === undefined)
|
|
697
|
+
return 'back';
|
|
698
|
+
if (!s3StagingDir)
|
|
699
|
+
return null;
|
|
700
|
+
const workgroup = await promptText(prompts, 'Athena workgroup (optional)\nPress Enter to use the default workgroup "primary".', stringConfigField(input.existingConnection, 'workgroup'));
|
|
701
|
+
if (workgroup === undefined)
|
|
702
|
+
return 'back';
|
|
703
|
+
const catalog = await promptText(prompts, 'Glue Data Catalog name (optional)\nPress Enter to use the default "AwsDataCatalog".', stringConfigField(input.existingConnection, 'catalog'));
|
|
704
|
+
if (catalog === undefined)
|
|
705
|
+
return 'back';
|
|
706
|
+
return {
|
|
707
|
+
driver: 'athena',
|
|
708
|
+
region,
|
|
709
|
+
s3_staging_dir: s3StagingDir,
|
|
710
|
+
...(workgroup ? { workgroup } : {}),
|
|
711
|
+
...(catalog ? { catalog } : {}),
|
|
712
|
+
...scriptedScopeConfigForDriver('athena', args.databaseSchemas),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
667
715
|
throw new Error(`Unsupported database driver: ${driver}`);
|
|
668
716
|
}
|
|
669
717
|
async function maybeApplyHistoricSqlConfig(input) {
|
|
@@ -1031,9 +1079,11 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1031
1079
|
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1032
1080
|
const connection = project.config.connections[input.connectionId];
|
|
1033
1081
|
const driver = normalizeDriver(connection?.driver);
|
|
1034
|
-
|
|
1082
|
+
const spec = driver ? SCOPE_DISCOVERY_SPECS[driver] : undefined;
|
|
1083
|
+
// Drivers with no scope spec are single-namespace (sqlite, duckdb): there is no
|
|
1084
|
+
// schema to choose, so skip the scope picker and ingest every table.
|
|
1085
|
+
if (!driver || !spec)
|
|
1035
1086
|
return okValidateResult();
|
|
1036
|
-
const spec = SCOPE_DISCOVERY_SPECS[driver];
|
|
1037
1087
|
const existingTables = connection?.enabled_tables;
|
|
1038
1088
|
const hasExistingTables = Array.isArray(existingTables) && existingTables.length > 0;
|
|
1039
1089
|
const existingScope = spec ? configuredScopeValues(connection, spec) : [];
|
package/dist/setup-sources.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type KtxProjectConnectionConfig } from './context/project/config.js';
|
|
|
3
3
|
import type { KtxCliIo } from './cli-runtime.js';
|
|
4
4
|
import { pickNotionRootPages } from './notion-page-picker.js';
|
|
5
5
|
import { type KtxSetupPromptOption } from './setup-prompts.js';
|
|
6
|
-
export type KtxSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion' | 'gdrive';
|
|
6
|
+
export type KtxSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion' | 'sigma' | 'gdrive';
|
|
7
7
|
export interface KtxSetupSourcesArgs {
|
|
8
8
|
projectDir: string;
|
|
9
9
|
inputMode: 'auto' | 'disabled';
|
|
@@ -99,6 +99,7 @@ export interface KtxSetupSourcesDeps {
|
|
|
99
99
|
validateLooker?: (projectDir: string, connectionId: string) => Promise<SourceValidationResult>;
|
|
100
100
|
validateLookml?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
|
|
101
101
|
validateNotion?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
|
|
102
|
+
validateSigma?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
|
|
102
103
|
validateGdrive?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
|
|
103
104
|
pickNotionRootPages?: typeof pickNotionRootPages;
|
|
104
105
|
discoverMetabaseDatabases?: (args: {
|
package/dist/setup-sources.js
CHANGED
|
@@ -10,6 +10,8 @@ import { createGoogleDocsClients, verifyGdriveFolderAndCountDocs, } from './cont
|
|
|
10
10
|
import { gdriveServiceAccountKeySchema } from './context/ingest/adapters/gdrive/types.js';
|
|
11
11
|
import { cloneOrPull, testRepoConnection } from './context/ingest/repo-fetch.js';
|
|
12
12
|
import { DEFAULT_METABASE_CLIENT_CONFIG, MetabaseClient } from './context/ingest/adapters/metabase/client.js';
|
|
13
|
+
import { DEFAULT_SIGMA_CLIENT_CONFIG, DefaultSigmaClient } from './context/ingest/adapters/sigma/client.js';
|
|
14
|
+
import { sigmaRuntimeConfigFromLocalConnection } from './context/ingest/adapters/sigma/local-sigma.adapter.js';
|
|
13
15
|
import { discoverMetabaseDatabases } from './context/ingest/adapters/metabase/mapping.js';
|
|
14
16
|
import { loadDbtSchemaFiles } from './context/ingest/dbt-shared/schema-files.js';
|
|
15
17
|
import { loadProjectInfo } from './context/ingest/dbt-shared/project-vars.js';
|
|
@@ -38,6 +40,7 @@ const SOURCE_OPTIONS = [
|
|
|
38
40
|
{ value: 'metricflow', label: 'MetricFlow' },
|
|
39
41
|
{ value: 'looker', label: 'Looker' },
|
|
40
42
|
{ value: 'lookml', label: 'LookML' },
|
|
43
|
+
{ value: 'sigma', label: 'Sigma Computing' },
|
|
41
44
|
{ value: 'gdrive', label: 'Google Drive' },
|
|
42
45
|
];
|
|
43
46
|
const SOURCE_LABELS = Object.fromEntries(SOURCE_OPTIONS.map((option) => [option.value, option.label]));
|
|
@@ -124,6 +127,7 @@ const SOURCE_CREDENTIAL_FLAG = {
|
|
|
124
127
|
notion: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
|
125
128
|
metabase: { field: 'sourceApiKeyRef', flag: '--source-api-key-ref' },
|
|
126
129
|
looker: { field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
|
|
130
|
+
sigma: { field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
|
|
127
131
|
gdrive: { field: null, flag: '--gdrive-service-account-key-ref' },
|
|
128
132
|
};
|
|
129
133
|
const ALL_SOURCE_CREDENTIAL_FLAGS = [
|
|
@@ -422,6 +426,17 @@ function buildNotionConnection(args) {
|
|
|
422
426
|
max_knowledge_updates_per_run: 20,
|
|
423
427
|
};
|
|
424
428
|
}
|
|
429
|
+
function buildSigmaConnection(args) {
|
|
430
|
+
if (!args.sourceClientId) {
|
|
431
|
+
throw new Error('Missing Sigma client id: pass --source-client-id.');
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
driver: 'sigma',
|
|
435
|
+
api_url: args.sourceUrl ?? 'https://api.sigmacomputing.com',
|
|
436
|
+
client_id: args.sourceClientId,
|
|
437
|
+
client_secret_ref: credentialRef(args.sourceClientSecretRef, 'Sigma client secret ref'),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
425
440
|
function buildGdriveConnection(args) {
|
|
426
441
|
const folderId = args.gdriveFolderId?.trim();
|
|
427
442
|
if (!folderId) {
|
|
@@ -544,6 +559,24 @@ async function defaultValidateNotion(connection) {
|
|
|
544
559
|
}
|
|
545
560
|
return { ok: true, detail: `roots=${roots.length}` };
|
|
546
561
|
}
|
|
562
|
+
async function defaultValidateSigma(connection) {
|
|
563
|
+
try {
|
|
564
|
+
const runtimeConfig = sigmaRuntimeConfigFromLocalConnection('sigma-main', connection);
|
|
565
|
+
const client = new DefaultSigmaClient(runtimeConfig, DEFAULT_SIGMA_CLIENT_CONFIG);
|
|
566
|
+
try {
|
|
567
|
+
const result = await client.testConnection();
|
|
568
|
+
return result.success
|
|
569
|
+
? { ok: true, detail: 'Sigma API connection verified' }
|
|
570
|
+
: { ok: false, message: result.error ?? 'Sigma connection test failed' };
|
|
571
|
+
}
|
|
572
|
+
finally {
|
|
573
|
+
await client.cleanup();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
547
580
|
async function defaultValidateGdrive(connection) {
|
|
548
581
|
const config = parseGdriveConnectionConfig(connection);
|
|
549
582
|
const keyText = await resolveGdriveServiceAccountKey(config.service_account_key_ref);
|
|
@@ -1110,6 +1143,45 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
1110
1143
|
},
|
|
1111
1144
|
]);
|
|
1112
1145
|
}
|
|
1146
|
+
if (source === 'sigma') {
|
|
1147
|
+
return await runSourcePromptSteps(initialState, () => [
|
|
1148
|
+
...connectionSteps,
|
|
1149
|
+
async (state) => {
|
|
1150
|
+
const sourceUrl = await promptText(prompts, {
|
|
1151
|
+
message: 'Sigma API URL',
|
|
1152
|
+
initialValue: state.sourceUrl ?? 'https://api.sigmacomputing.com',
|
|
1153
|
+
});
|
|
1154
|
+
if (sourceUrl === undefined)
|
|
1155
|
+
return 'back';
|
|
1156
|
+
state.sourceUrl = sourceUrl;
|
|
1157
|
+
return 'next';
|
|
1158
|
+
},
|
|
1159
|
+
async (state) => {
|
|
1160
|
+
const sourceClientId = await promptText(prompts, {
|
|
1161
|
+
message: 'Sigma client ID',
|
|
1162
|
+
...(state.sourceClientId ? { initialValue: state.sourceClientId } : {}),
|
|
1163
|
+
});
|
|
1164
|
+
if (sourceClientId === undefined)
|
|
1165
|
+
return 'back';
|
|
1166
|
+
state.sourceClientId = sourceClientId;
|
|
1167
|
+
return 'next';
|
|
1168
|
+
},
|
|
1169
|
+
async (state) => {
|
|
1170
|
+
const ref = await chooseSourceCredentialRef({
|
|
1171
|
+
prompts,
|
|
1172
|
+
projectDir: args.projectDir,
|
|
1173
|
+
label: 'Sigma client secret',
|
|
1174
|
+
envName: 'SIGMA_CLIENT_SECRET',
|
|
1175
|
+
secretFileName: `${state.sourceConnectionId ?? 'sigma-main'}-client-secret`,
|
|
1176
|
+
existingRef: state.sourceClientSecretRef,
|
|
1177
|
+
});
|
|
1178
|
+
if (ref === 'back')
|
|
1179
|
+
return 'back';
|
|
1180
|
+
state.sourceClientSecretRef = ref;
|
|
1181
|
+
return 'next';
|
|
1182
|
+
},
|
|
1183
|
+
]);
|
|
1184
|
+
}
|
|
1113
1185
|
if (source === 'notion') {
|
|
1114
1186
|
return await runSourcePromptSteps(initialState, (state) => [
|
|
1115
1187
|
...connectionSteps,
|
|
@@ -1355,6 +1427,12 @@ function sourceArgsFromExistingConnection(input) {
|
|
|
1355
1427
|
}
|
|
1356
1428
|
return sourceArgs;
|
|
1357
1429
|
}
|
|
1430
|
+
if (input.source === 'sigma') {
|
|
1431
|
+
sourceArgs.sourceUrl = stringField(input.connection.api_url) ?? undefined;
|
|
1432
|
+
sourceArgs.sourceClientId = stringField(input.connection.client_id) ?? undefined;
|
|
1433
|
+
sourceArgs.sourceClientSecretRef = stringField(input.connection.client_secret_ref) ?? undefined;
|
|
1434
|
+
return sourceArgs;
|
|
1435
|
+
}
|
|
1358
1436
|
if (input.source === 'gdrive') {
|
|
1359
1437
|
sourceArgs.gdriveServiceAccountKeyRef = stringField(input.connection.service_account_key_ref);
|
|
1360
1438
|
sourceArgs.gdriveFolderId = stringField(input.connection.folder_id);
|
|
@@ -1488,6 +1566,9 @@ function buildConnection(source, args) {
|
|
|
1488
1566
|
if (source === 'lookml') {
|
|
1489
1567
|
return buildLookmlConnection(args);
|
|
1490
1568
|
}
|
|
1569
|
+
if (source === 'sigma') {
|
|
1570
|
+
return buildSigmaConnection(args);
|
|
1571
|
+
}
|
|
1491
1572
|
if (source === 'notion') {
|
|
1492
1573
|
return buildNotionConnection(args);
|
|
1493
1574
|
}
|
|
@@ -1511,6 +1592,9 @@ async function validateSource(source, args, deps) {
|
|
|
1511
1592
|
if (source === 'lookml') {
|
|
1512
1593
|
return await (deps.validateLookml ?? defaultValidateLookml)(args.connection);
|
|
1513
1594
|
}
|
|
1595
|
+
if (source === 'sigma') {
|
|
1596
|
+
return await (deps.validateSigma ?? defaultValidateSigma)(args.connection);
|
|
1597
|
+
}
|
|
1514
1598
|
if (source === 'notion') {
|
|
1515
1599
|
return await (deps.validateNotion ?? defaultValidateNotion)(args.connection);
|
|
1516
1600
|
}
|