@@ -43,6 +43,8 @@ function apocRemoveFromArray(arrayClause: string) {
43
43
return `apoc.coll.removeAll(${ arrayClause } , ["${ RUSHDB_VALUE_EMPTY_ARRAY } "])`
44
44
}
45
45
46
+ const INCLUDE_OWN_PROPERTIES = '.*' as const
47
+
46
48
function parseAggregate (
47
49
aggregate : Aggregate ,
48
50
aliasesMap : AliasesMap ,
@@ -107,12 +109,42 @@ function parseAggregate(
107
109
108
110
export function buildAggregation ( aggregate : Aggregate , aliasesMap : AliasesMap ) {
109
111
if ( isObject ( aggregate ) && Object . keys ( aggregate ) . length ) {
110
- const isNested = Object . values ( aggregate ) . some ( ( instruction ) => isObject ( instruction . aggregate ) )
112
+ const entries = Object . values ( aggregate ) as Array < any >
113
+
114
+ const isNested = entries . some ( ( instruction ) => isObject ( instruction ?. aggregate ) )
115
+ const usesOnlyTopLevelRecordAlias =
116
+ ! isNested && entries . length > 0 && entries . every ( ( instruction ) => instruction ?. alias === '$record' )
117
+
118
+ // TOP-LEVEL AGGREGATIONS ONLY: compute global aggregates once and return a single element
119
+ if ( usesOnlyTopLevelRecordAlias ) {
120
+ const withAggregations : string [ ] = [ ]
121
+ const orderClauses : string [ ] = [ ]
122
+
123
+ const fieldsInCollect : string [ ] = [ ] // unused here
124
+ parseAggregate ( aggregate , aliasesMap , {
125
+ fieldsInCollect,
126
+ withAggregations,
127
+ orderClauses
128
+ } )
129
+
130
+ // Build WITH without `record`
131
+ const withPart = withAggregations . length ? `WITH ${ withAggregations . join ( ', ' ) } ` : ''
132
+
133
+ // Build the single map projection out of the aggregate keys
134
+ const aggKeys = Object . keys ( aggregate ) // e.g. ["minPrice","maxPrice","avgPrice"]
135
+ const oneMap = `{ ${ aggKeys . map ( ( k ) => `${ k } : ${ k } ` ) . join ( ', ' ) } }`
136
+
137
+ return {
138
+ withPart,
139
+ // Single-element array so downstream expects `records` array but gets exactly one element
140
+ recordPart : `[ ${ oneMap } ] AS records`
141
+ }
142
+ }
111
143
112
144
if ( isNested ) {
113
145
// Add first level aliases to RETURN clause
114
146
const fieldsInCollect : string [ ] = [
115
- '.*' ,
147
+ INCLUDE_OWN_PROPERTIES ,
116
148
`${ label ( ) } ` ,
117
149
...Object . keys ( aggregate ) . map ( ( key ) => `\`${ key } \`` )
118
150
]
@@ -128,10 +160,10 @@ export function buildAggregation(aggregate: Aggregate, aliasesMap: AliasesMap) {
128
160
recordPart : `collect(DISTINCT record {${ fieldsInCollect . join ( ', ' ) } }) AS records`
129
161
}
130
162
} else {
131
- const fieldsInCollect : string [ ] = [ '.*' , `${ label ( ) } ` ]
163
+ const fieldsInCollect : string [ ] = [ INCLUDE_OWN_PROPERTIES , `${ label ( ) } ` ]
132
164
133
- const withAggregations = [ ]
134
- const orderClauses = [ ]
165
+ const withAggregations : string [ ] = [ ]
166
+ const orderClauses : string [ ] = [ ]
135
167
136
168
parseAggregate ( aggregate , aliasesMap , {
137
169
fieldsInCollect,
@@ -141,17 +173,17 @@ export function buildAggregation(aggregate: Aggregate, aliasesMap: AliasesMap) {
141
173
142
174
const withPart = withAggregations . length ? `WITH record, ${ withAggregations . join ( ', ' ) } ` : ''
143
175
144
- // Combine the return clause
145
176
return {
146
177
withPart,
147
178
recordPart : `collect(DISTINCT record {${ fieldsInCollect . join ( ', ' ) } }) AS records`
148
179
}
149
180
}
150
181
}
151
182
183
+ // No aggregations provided
152
184
return {
153
185
withPart : '' ,
154
- recordPart : `collect(DISTINCT record {.* , ${ label ( ) } }) AS records`
186
+ recordPart : `collect(DISTINCT record {${ INCLUDE_OWN_PROPERTIES } , ${ label ( ) } }) AS records`
155
187
}
156
188
}
157
189
@@ -242,7 +274,7 @@ export function buildCollectFunction(
242
274
}
243
275
244
276
return `${ apocSortMapsArray (
245
- `collect(${ uniq } ${ alias } ${ propertyName } {.* , ${ label ( alias ) } })` ,
277
+ `collect(${ uniq } ${ alias } ${ propertyName } {${ INCLUDE_OWN_PROPERTIES } , ${ label ( alias ) } })` ,
246
278
instruction . orderBy
247
279
) } [${ skip } ..${ limit } ] AS \`${ returnAlias } \``
248
280
}
@@ -290,7 +322,7 @@ export function parseBottomUpQuery(
290
322
}
291
323
292
324
const collectPart = `collect(DISTINCT ${ currentRecord } {${ [
293
- '.*' ,
325
+ INCLUDE_OWN_PROPERTIES ,
294
326
...collectParts . map ( ( variable ) => `\`${ variable } \`` ) ,
295
327
label ( currentRecord )
296
328
] . join ( ', ' ) } })`
0 commit comments