From f5949a1e8680b077058bfcc2c31845d603beb53b Mon Sep 17 00:00:00 2001 From: Ayush Jhanwar Date: Fri, 26 Jun 2026 14:20:56 +0530 Subject: [PATCH] fix(db): qualify event-level properties access in funnel & conversion queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a funnel mixes a profile.* filter with an event-level properties.* filter on different steps, or a conversion has a profile.* breakdown alongside an event-level properties.* filter, ClickHouse fails with: JOIN ... ambiguous identifier 'properties' Root cause: the profile JOIN selects the 'properties' Map from profiles. The windowFunnel step conditions then have two columns named 'properties' in scope — events.properties and profile.properties — and any unqualified properties['key'] reference becomes ambiguous. Fix: pass eventsAlias='events' to getEventFiltersWhereClause in funnel.service.ts:getFunnelConditions and conversion.service.ts where- clause builders. This reuses the existing eventsAlias plumbing in getSelectPropertyKey (lines 322-328, already used by other call sites at chart.service.ts:448 and :866 with 'e' alias), so event-level properties[...] becomes events.properties[...] — explicitly bound to the events table, no ambiguity with profile.properties[...]. --- packages/db/src/services/conversion.service.ts | 10 ++++++++-- packages/db/src/services/funnel.service.ts | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/db/src/services/conversion.service.ts b/packages/db/src/services/conversion.service.ts index 347191b33..fc721722d 100644 --- a/packages/db/src/services/conversion.service.ts +++ b/packages/db/src/services/conversion.service.ts @@ -118,11 +118,17 @@ export class ConversionService { const eventA = events[0]!; const eventB = events[1]!; + // Qualify with 'events' so event-level `properties[...]` becomes + // `events.properties[...]` — required when the conversion query also + // joins the profiles table (which exposes a `properties` column). + // Without the qualifier ClickHouse fails with "ambiguous identifier + // 'properties'" whenever a step filters on properties.X while a + // breakdown is on profile.properties.Y. const whereA = Object.values( - getEventFiltersWhereClause(eventA.filters, projectId), + getEventFiltersWhereClause(eventA.filters, projectId, 'events'), ).join(' AND '); const whereB = Object.values( - getEventFiltersWhereClause(eventB.filters, projectId), + getEventFiltersWhereClause(eventB.filters, projectId, 'events'), ).join(' AND '); const funnelWindowSeconds = funnelWindow * 3600; diff --git a/packages/db/src/services/funnel.service.ts b/packages/db/src/services/funnel.service.ts index 442c34df5..5a9ce8335 100644 --- a/packages/db/src/services/funnel.service.ts +++ b/packages/db/src/services/funnel.service.ts @@ -42,7 +42,13 @@ export class FunnelService { getFunnelConditions(events: IChartEvent[] = [], projectId?: string): string[] { return events.map((event) => { const { sb, getWhere } = createSqlBuilder(); - sb.where = getEventFiltersWhereClause(event.filters, projectId); + // Qualify with 'events' so event-level `properties[...]` becomes + // `events.properties[...]` — required because the funnel CTE may join + // the profiles table (which also exposes a `properties` column). + // Without the qualifier ClickHouse fails with "ambiguous identifier + // 'properties'" whenever a step filters on properties.X while another + // step filters on profile.properties.Y. + sb.where = getEventFiltersWhereClause(event.filters, projectId, 'events'); sb.where.name = `events.name = ${sqlstring.escape(event.name)}`; return getWhere().replace('WHERE ', ''); });