( pipeline: NamespacedAndKeyedStream, groupByClause: GroupBy, havingClauses?: Array<Having>, selectClause?: Select, fnHavingClauses?: Array<(row: any) => any>, aggregateCollectionId?: string, mainSource?: string, )
| 125 | * Works with the new $selected structure from early SELECT processing |
| 126 | */ |
| 127 | export function processGroupBy( |
| 128 | pipeline: NamespacedAndKeyedStream, |
| 129 | groupByClause: GroupBy, |
| 130 | havingClauses?: Array<Having>, |
| 131 | selectClause?: Select, |
| 132 | fnHavingClauses?: Array<(row: any) => any>, |
| 133 | aggregateCollectionId?: string, |
| 134 | mainSource?: string, |
| 135 | ): NamespacedAndKeyedStream { |
| 136 | const virtualAggregates: Record<string, any> = { |
| 137 | [VIRTUAL_SYNCED_KEY]: { |
| 138 | preMap: ([, row]: [string, NamespacedRow]) => |
| 139 | getRowVirtualMetadata(row).synced, |
| 140 | reduce: (values: Array<[boolean, number]>) => { |
| 141 | for (const [isSynced, multiplicity] of values) { |
| 142 | if (!isSynced && multiplicity > 0) { |
| 143 | return false |
| 144 | } |
| 145 | } |
| 146 | return true |
| 147 | }, |
| 148 | }, |
| 149 | [VIRTUAL_HAS_LOCAL_KEY]: { |
| 150 | preMap: ([, row]: [string, NamespacedRow]) => |
| 151 | getRowVirtualMetadata(row).hasLocal, |
| 152 | reduce: (values: Array<[boolean, number]>) => { |
| 153 | for (const [isLocal, multiplicity] of values) { |
| 154 | if (isLocal && multiplicity > 0) { |
| 155 | return true |
| 156 | } |
| 157 | } |
| 158 | return false |
| 159 | }, |
| 160 | }, |
| 161 | } |
| 162 | |
| 163 | // Handle empty GROUP BY (single-group aggregation) |
| 164 | if (groupByClause.length === 0) { |
| 165 | // For single-group aggregation, create a single group with all data |
| 166 | const aggregates: Record<string, any> = virtualAggregates |
| 167 | |
| 168 | // Expressions that wrap aggregates (e.g. coalesce(count(...), 0)). |
| 169 | // Keys are the original SELECT aliases; values are pre-compiled evaluators |
| 170 | // over the transformed (aggregate-free) expression. |
| 171 | const wrappedAggExprs: Record<string, (data: any) => any> = {} |
| 172 | const aggCounter = { value: 0 } |
| 173 | |
| 174 | if (selectClause) { |
| 175 | // Scan the SELECT clause for aggregate functions |
| 176 | for (const [alias, expr] of Object.entries(selectClause)) { |
| 177 | if (expr.type === `agg`) { |
| 178 | aggregates[alias] = getAggregateFunction(expr) |
| 179 | } else if (containsAggregate(expr)) { |
| 180 | const { transformed, extracted } = extractAndReplaceAggregates( |
| 181 | expr as SelectValueExpression, |
| 182 | aggCounter, |
| 183 | ) |
| 184 | for (const [syntheticAlias, aggExpr] of Object.entries(extracted)) { |
no test coverage detected