From af58c6c4347ea2948860d8a8b4c2c2990840a950 Mon Sep 17 00:00:00 2001 From: Alex Fedotyev <61838744+alex-fedotyev@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:18:50 +0000 Subject: [PATCH 1/2] fix(search): keep select-alias filters working in Event Patterns Filtering on a column the source exposes only under an alias (for example a default select of `ServiceName as service`) failed in the Event Patterns view with "Unknown expression or table expression identifier 'service'". The results table works because its own SELECT defines the alias, but Event Patterns rebuilds the SELECT and did not carry the alias definitions. Thread the source's alias WITH clauses (the same `aliasWith` already passed to the results, histogram, and heatmap queries) into the PatternTable config so the rebuilt pattern query defines the alias and the filter resolves. Co-Authored-By: Claude Opus 4.8 --- .changeset/pattern-table-alias-with.md | 13 +++++ packages/app/src/DBSearchPage.tsx | 6 ++ .../src/__tests__/renderChartConfig.test.ts | 56 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 .changeset/pattern-table-alias-with.md diff --git a/.changeset/pattern-table-alias-with.md b/.changeset/pattern-table-alias-with.md new file mode 100644 index 0000000000..7d37aa7fef --- /dev/null +++ b/.changeset/pattern-table-alias-with.md @@ -0,0 +1,13 @@ +--- +'@hyperdx/app': patch +--- + +fix(search): keep select-alias filters working in Event Patterns + +Filtering on a column the source exposes only under an alias (for example a +default select of `ServiceName as service`) failed in the Event Patterns view +with `Unknown expression or table expression identifier 'service'`. The +results table works because its own SELECT defines the alias, but Event +Patterns rebuilds the SELECT and did not carry the alias definitions. The +pattern query now receives the same alias `WITH` clauses already threaded into +the results, histogram, and heatmap queries, so the filter resolves. diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index 4cfdda7a29..372aa0e201 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -2193,6 +2193,12 @@ export function DBSearchPage() { config={{ ...chartConfig, dateRange: searchedTimeRange, + // Carry the source's select-alias definitions so the + // rebuilt pattern query can filter on aliased columns + // (e.g. `ServiceName as service`) without hitting + // "Unknown identifier". Mirrors the results, + // histogram, and heatmap configs. + with: aliasWith, }} bodyValueExpression={ searchedSource diff --git a/packages/common-utils/src/__tests__/renderChartConfig.test.ts b/packages/common-utils/src/__tests__/renderChartConfig.test.ts index 35a198d7ea..028673739f 100644 --- a/packages/common-utils/src/__tests__/renderChartConfig.test.ts +++ b/packages/common-utils/src/__tests__/renderChartConfig.test.ts @@ -1,6 +1,7 @@ import { chSql, ColumnMeta, parameterizedQueryToSql } from '@/clickhouse'; import { Metadata } from '@/core/metadata'; import { + BuilderChartConfig, ChartConfigWithOptDateRange, DisplayType, MetricsDataType, @@ -1215,6 +1216,61 @@ describe('renderChartConfig', () => { }); }); + describe('Event Patterns query with select-alias filter (HDX-1879)', () => { + // The Event Patterns view rebuilds the SELECT (sampled body + timestamp, + // ORDER BY rand() LIMIT) instead of reusing the results-table SELECT. When + // the user filters on a column the source exposes only under an alias + // (e.g. `ServiceName as service`), that alias is out of scope in the + // rebuilt query unless its definition is carried through `with`. Threading + // the source's alias map into the pattern config defines the alias in a + // WITH clause so the filter resolves. + const patternConfig = ( + withClauses: BuilderChartConfig['with'], + ): ChartConfigWithOptDateRange => ({ + connection: 'test-connection', + from: { databaseName: 'default', tableName: 'otel_logs' }, + with: withClauses, + select: 'Body as __hdx_pattern_field, Timestamp as __hdx_timestamp', + where: "service = 'api'", + whereLanguage: 'sql', + orderBy: [{ ordering: 'DESC', valueExpression: 'rand()' }], + limit: { limit: 10000 }, + timestampValueExpression: 'Timestamp', + dateRange: [new Date('2025-01-01'), new Date('2025-01-02')], + }); + + it('defines the select alias in a WITH clause so the filter resolves', async () => { + const generatedSql = await renderChartConfig( + patternConfig([ + { name: 'service', sql: chSql`ServiceName`, isSubquery: false }, + ]), + mockMetadata, + querySettings, + ); + const sql = parameterizedQueryToSql(generatedSql); + + // Alias is defined in the rebuilt pattern query... + expect(sql).toContain('(ServiceName) AS service'); + // ...and the filter still references it. + expect(sql).toContain("service = 'api'"); + }); + + it('omits the alias definition when no alias map is threaded (the bug)', async () => { + const generatedSql = await renderChartConfig( + patternConfig(undefined), + mockMetadata, + querySettings, + ); + const sql = parameterizedQueryToSql(generatedSql); + + // Without the threaded WITH clauses the alias is undefined, so the + // filter references a `service` column that does not exist in the + // rebuilt SELECT (ClickHouse rejects this with "Unknown identifier"). + expect(sql).not.toContain('AS service'); + expect(sql).toContain("service = 'api'"); + }); + }); + describe('SQL filter KV items direct_read optimization', () => { const stubKvItemsMetadata = () => { mockMetadata.getColumns = jest.fn().mockResolvedValue([ From 3d517071f2d846c25895830b2d55ea46c171db74 Mon Sep 17 00:00:00 2001 From: Alex Fedotyev <61838744+alex-fedotyev@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:28:25 +0000 Subject: [PATCH 2/2] chore: remove stray em-dashes from renderChartConfig test comments --- .../common-utils/src/__tests__/renderChartConfig.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/common-utils/src/__tests__/renderChartConfig.test.ts b/packages/common-utils/src/__tests__/renderChartConfig.test.ts index 028673739f..8b66235c10 100644 --- a/packages/common-utils/src/__tests__/renderChartConfig.test.ts +++ b/packages/common-utils/src/__tests__/renderChartConfig.test.ts @@ -553,7 +553,7 @@ describe('renderChartConfig', () => { ); // Two chunked windows of the same chart (most recent window first, - // older windows are end-exclusive — mirrors fetchDataInChunks). + // older windows are end-exclusive, mirroring fetchDataInChunks). const recentChunk = await renderWindow( [new Date('2025-02-12T18:00:00Z'), rankingRange[1]], true, @@ -563,7 +563,7 @@ describe('renderChartConfig', () => { false, ); - // The CTE end is the first `) SELECT` — the outer query starts there. + // The CTE end is the first `) SELECT`; the outer query starts there. const cteOf = (sql: string) => { const start = sql.indexOf('`__hdx_series_limit` AS ('); const end = sql.indexOf(') SELECT '); @@ -668,7 +668,7 @@ describe('renderChartConfig', () => { ), ); expect(sql).toContain('__hdx_series_limit'); - // Each column gets its own NULL check, split on the top-level comma — not + // Each column gets its own NULL check, split on the top-level comma, not // the comma inside Map['...']. expect(sql).toMatch( /LogAttributes\[['"]agentToServer\.capabilities['"]\]\s+IS\s+NOT\s+NULL/, @@ -3282,7 +3282,7 @@ describe('renderChartConfig', () => { undefined, ); - // PromQL configs return empty SQL — queries go through the Prometheus API route + // PromQL configs return empty SQL; queries go through the Prometheus API route expect(generatedSql.sql).toBe(''); expect(generatedSql.params).toEqual({}); });