@@ -1088,15 +1088,20 @@ export class ClickHousePrinter {
10881088
10891089 // Look up table schema and get ClickHouse table name
10901090 const tableSchema = this . lookupTable ( tableName ) ;
1091- joinStrings . push ( tableSchema . clickhouseName ) ;
1091+
1092+ // Always add the TSQL table name as an alias if no explicit alias is provided
1093+ // This ensures table-qualified column references work in WHERE clauses
1094+ // (needed to avoid alias conflicts when columns have expressions)
1095+ const effectiveAlias = node . alias || tableName ;
1096+ joinStrings . push (
1097+ `${ tableSchema . clickhouseName } AS ${ this . printIdentifier ( effectiveAlias ) } `
1098+ ) ;
10921099
10931100 // Register this table context for column name resolution
1094- // Use the alias if provided, otherwise use the TSQL table name
1095- const contextKey = node . alias || tableName ;
1096- this . tableContexts . set ( contextKey , tableSchema ) ;
1101+ this . tableContexts . set ( effectiveAlias , tableSchema ) ;
10971102
10981103 // Add tenant isolation guard
1099- extraWhere = this . createTenantGuard ( tableSchema , node . alias || tableName ) ;
1104+ extraWhere = this . createTenantGuard ( tableSchema , effectiveAlias ) ;
11001105 } else if (
11011106 ( tableExpr as SelectQuery ) . expression_type === "select_query" ||
11021107 ( tableExpr as SelectSetQuery ) . expression_type === "select_set_query"
@@ -1638,6 +1643,18 @@ export class ClickHousePrinter {
16381643 }
16391644
16401645 // Check if this field is a virtual column
1646+ // BUT: if the column has whereTransform and we're in a comparison context,
1647+ // use the raw column instead of the expression (for efficient index usage)
1648+ const columnSchema = this . resolveFieldToColumnSchema ( node . chain ) ;
1649+ const inComparisonContext = this . isInComparisonContext ( ) ;
1650+
1651+ if ( columnSchema ?. whereTransform && inComparisonContext ) {
1652+ // Use raw column for WHERE comparisons when whereTransform is defined
1653+ // Must table-qualify to avoid alias conflicts with SELECT expressions
1654+ const tableQualifiedChain = this . resolveFieldChainWithTableAlias ( node . chain ) ;
1655+ return tableQualifiedChain . map ( ( part ) => this . printIdentifierOrIndex ( part ) ) . join ( "." ) ;
1656+ }
1657+
16411658 const virtualExpression = this . getVirtualColumnExpressionForField ( node . chain ) ;
16421659 if ( virtualExpression !== null ) {
16431660 // Return the expression wrapped in parentheses
@@ -1651,6 +1668,81 @@ export class ClickHousePrinter {
16511668 return resolvedChain . map ( ( part ) => this . printIdentifierOrIndex ( part ) ) . join ( "." ) ;
16521669 }
16531670
1671+ /**
1672+ * Check if we're currently inside a comparison operation (WHERE context)
1673+ */
1674+ private isInComparisonContext ( ) : boolean {
1675+ for ( const node of this . stack ) {
1676+ if ( ( node as CompareOperation ) . expression_type === "compare_operation" ) {
1677+ return true ;
1678+ }
1679+ }
1680+ return false ;
1681+ }
1682+
1683+ /**
1684+ * Resolve field chain with table alias prefix to avoid alias conflicts.
1685+ * This is used in WHERE clauses when a column has whereTransform to ensure
1686+ * we reference the raw column, not a SELECT alias with the same name.
1687+ */
1688+ private resolveFieldChainWithTableAlias ( chain : Array < string | number > ) : Array < string | number > {
1689+ if ( chain . length === 0 ) return chain ;
1690+
1691+ const firstPart = chain [ 0 ] ;
1692+ if ( typeof firstPart !== "string" ) return chain ;
1693+
1694+ // If already qualified (table.column), use normal resolution
1695+ if ( chain . length >= 2 ) {
1696+ return this . resolveFieldChain ( chain ) ;
1697+ }
1698+
1699+ // Unqualified reference - need to find the table and add its alias
1700+ const columnName = firstPart ;
1701+ for ( const [ tableAlias , tableSchema ] of this . tableContexts . entries ( ) ) {
1702+ const columnSchema = tableSchema . columns [ columnName ] ;
1703+ if ( columnSchema ) {
1704+ const resolvedColumnName = columnSchema . clickhouseName || columnSchema . name ;
1705+ return [ tableAlias , resolvedColumnName , ...chain . slice ( 1 ) ] ;
1706+ }
1707+ }
1708+
1709+ // Not found in any table - return as-is
1710+ return chain ;
1711+ }
1712+
1713+ /**
1714+ * Resolve a field chain to its column schema (if it references a known column)
1715+ */
1716+ private resolveFieldToColumnSchema ( chain : Array < string | number > ) : ColumnSchema | null {
1717+ if ( chain . length === 0 ) return null ;
1718+
1719+ const firstPart = chain [ 0 ] ;
1720+ if ( typeof firstPart !== "string" ) return null ;
1721+
1722+ // Qualified reference: table.column
1723+ if ( chain . length >= 2 ) {
1724+ const tableAlias = firstPart ;
1725+ const tableSchema = this . tableContexts . get ( tableAlias ) ;
1726+ if ( ! tableSchema ) return null ;
1727+
1728+ const columnName = chain [ 1 ] ;
1729+ if ( typeof columnName !== "string" ) return null ;
1730+
1731+ return tableSchema . columns [ columnName ] || null ;
1732+ }
1733+
1734+ // Unqualified reference
1735+ const columnName = firstPart ;
1736+ for ( const tableSchema of this . tableContexts . values ( ) ) {
1737+ const columnSchema = tableSchema . columns [ columnName ] ;
1738+ if ( columnSchema ) {
1739+ return columnSchema ;
1740+ }
1741+ }
1742+
1743+ return null ;
1744+ }
1745+
16541746 /**
16551747 * Check if a field chain references a virtual column and return its expression
16561748 * @returns The virtual column expression, or null if not a virtual column
0 commit comments