From b664d5c3d8713d57e6e1822304ad131aaa7e6b23 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Fri, 22 May 2026 15:01:14 +0200 Subject: [PATCH] fix(scan): unblock relationship discovery on Snowflake Two adjacent bugs prevented the scan's relationship pipeline from producing any joins on a Snowflake warehouse: - relationship-profiling.ts fell through to a default `GROUP_CONCAT` branch for unknown drivers. Snowflake has no GROUP_CONCAT, so every per-table profile query failed with "Unknown function GROUP_CONCAT". Add an explicit Snowflake branch that uses LISTAGG with a literal '\x1f' delimiter (Snowflake requires the delimiter to be a constant, so CHR(31) is rejected). - description-generation.ts destructured `connector.sampleTable` and `connector.sampleColumn` into bare locals, losing the `this` binding when the class-method connectors (Snowflake, Postgres, MySQL) were invoked. Every sample call threw "Cannot read properties of undefined (reading 'assertConnection')" and degraded LLM descriptions to metadata-only prompts. Call the methods through the connector instead. Without these, even after the primary-key probe is allowed to fail softly, the scan ends up with 0 validated relationships and an empty `joins:` block in every shard YAML. --- .../cli/src/context/scan/description-generation.ts | 10 +++++----- .../cli/src/context/scan/relationship-profiling.ts | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/context/scan/description-generation.ts b/packages/cli/src/context/scan/description-generation.ts index 4526215d..640ae1a6 100644 --- a/packages/cli/src/context/scan/description-generation.ts +++ b/packages/cli/src/context/scan/description-generation.ts @@ -463,11 +463,11 @@ export class KtxDescriptionGenerator { } } - const sampleTable = input.connector.sampleTable; + const connector = input.connector; let sampleData: KtxTableSampleResult | null = null; let fallbackReason: 'capability_missing' | 'sampling_failed' | 'empty_sample' | null = null; - if (!sampleTable) { + if (!connector.sampleTable) { fallbackReason = 'capability_missing'; this.logger?.warn('KTX scan connector does not support table sampling; falling back to metadata-only prompt', { connectorId: input.connector.id, @@ -484,7 +484,7 @@ export class KtxDescriptionGenerator { try { sampleData = await retryAsync( () => - sampleTable( + connector.sampleTable!( { connectionId: input.connectionId, table: tableRef, @@ -684,11 +684,11 @@ export class KtxDescriptionGenerator { }); columnValues = []; } else { - const sampleColumn = input.connector.sampleColumn; + const connector = input.connector; try { const sample = await retryAsync( () => - sampleColumn( + connector.sampleColumn!( { connectionId: input.connectionId, table: tableRef, diff --git a/packages/cli/src/context/scan/relationship-profiling.ts b/packages/cli/src/context/scan/relationship-profiling.ts index 2172ac24..77f1c38d 100644 --- a/packages/cli/src/context/scan/relationship-profiling.ts +++ b/packages/cli/src/context/scan/relationship-profiling.ts @@ -227,6 +227,9 @@ function sampleAggregateSql(driver: KtxConnectionDriver, innerSql: string): stri if (driver === 'clickhouse') { return `(SELECT arrayStringConcat(groupArray(toString(value)), '\\x1F') FROM (${innerSql}) AS relationship_profile_values)`; } + if (driver === 'snowflake') { + return `(SELECT LISTAGG(CAST(value AS VARCHAR), '\\x1f') FROM (${innerSql}) AS relationship_profile_values)`; + } return `(SELECT GROUP_CONCAT(CAST(value AS TEXT), char(31)) FROM (${innerSql}) AS relationship_profile_values)`; }