* Validates that all non-aggregate expressions in SELECT are present in GROUP BY * and creates a cached mapping for efficient lookup during processing
( groupByClause: GroupBy, selectClause?: Select, )
| 87 | * and creates a cached mapping for efficient lookup during processing |
| 88 | */ |
| 89 | function validateAndCreateMapping( |
| 90 | groupByClause: GroupBy, |
| 91 | selectClause?: Select, |
| 92 | ): GroupBySelectMapping { |
| 93 | const selectToGroupByIndex = new Map<string, number>() |
| 94 | const groupByExpressions = [...groupByClause] |
| 95 | |
| 96 | if (!selectClause) { |
| 97 | return { selectToGroupByIndex, groupByExpressions } |
| 98 | } |
| 99 | |
| 100 | // Validate each SELECT expression |
| 101 | for (const [alias, expr] of Object.entries(selectClause)) { |
| 102 | if (expr.type === `agg` || containsAggregate(expr)) { |
| 103 | // Aggregate expressions (plain or wrapped) are allowed and don't need to be in GROUP BY |
| 104 | continue |
| 105 | } |
| 106 | |
| 107 | // Non-aggregate expression must be in GROUP BY |
| 108 | const groupIndex = groupByExpressions.findIndex((groupExpr) => |
| 109 | expressionsEqual(expr, groupExpr), |
| 110 | ) |
| 111 | |
| 112 | if (groupIndex === -1) { |
| 113 | throw new NonAggregateExpressionNotInGroupByError(alias) |
| 114 | } |
| 115 | |
| 116 | // Cache the mapping |
| 117 | selectToGroupByIndex.set(alias, groupIndex) |
| 118 | } |
| 119 | |
| 120 | return { selectToGroupByIndex, groupByExpressions } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Processes the GROUP BY clause with optional HAVING and SELECT |
no test coverage detected