Skip to content

Commit e1045f4

Browse files
authored
Merge pull request #233 from rush-db/feat/top-level-aggregations
Improved top level aggregations
2 parents 01a981b + 83b7ee4 commit e1045f4

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

.changeset/olive-houses-help.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'rushdb-core': minor
3+
'rushdb-docs': minor
4+
'@rushdb/javascript-sdk': minor
5+
'rushdb-dashboard': minor
6+
'rushdb-website': minor
7+
---
8+
9+
Top level aggregations now works in a predicatable manner

platform/core/src/core/search/parser/aggregate.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ function apocRemoveFromArray(arrayClause: string) {
4343
return `apoc.coll.removeAll(${arrayClause}, ["${RUSHDB_VALUE_EMPTY_ARRAY}"])`
4444
}
4545

46+
const INCLUDE_OWN_PROPERTIES = '.*' as const
47+
4648
function parseAggregate(
4749
aggregate: Aggregate,
4850
aliasesMap: AliasesMap,
@@ -107,12 +109,42 @@ function parseAggregate(
107109

108110
export function buildAggregation(aggregate: Aggregate, aliasesMap: AliasesMap) {
109111
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+
}
111143

112144
if (isNested) {
113145
// Add first level aliases to RETURN clause
114146
const fieldsInCollect: string[] = [
115-
'.*',
147+
INCLUDE_OWN_PROPERTIES,
116148
`${label()}`,
117149
...Object.keys(aggregate).map((key) => `\`${key}\``)
118150
]
@@ -128,10 +160,10 @@ export function buildAggregation(aggregate: Aggregate, aliasesMap: AliasesMap) {
128160
recordPart: `collect(DISTINCT record {${fieldsInCollect.join(', ')}}) AS records`
129161
}
130162
} else {
131-
const fieldsInCollect: string[] = ['.*', `${label()}`]
163+
const fieldsInCollect: string[] = [INCLUDE_OWN_PROPERTIES, `${label()}`]
132164

133-
const withAggregations = []
134-
const orderClauses = []
165+
const withAggregations: string[] = []
166+
const orderClauses: string[] = []
135167

136168
parseAggregate(aggregate, aliasesMap, {
137169
fieldsInCollect,
@@ -141,17 +173,17 @@ export function buildAggregation(aggregate: Aggregate, aliasesMap: AliasesMap) {
141173

142174
const withPart = withAggregations.length ? `WITH record, ${withAggregations.join(', ')}` : ''
143175

144-
// Combine the return clause
145176
return {
146177
withPart,
147178
recordPart: `collect(DISTINCT record {${fieldsInCollect.join(', ')}}) AS records`
148179
}
149180
}
150181
}
151182

183+
// No aggregations provided
152184
return {
153185
withPart: '',
154-
recordPart: `collect(DISTINCT record {.*, ${label()}}) AS records`
186+
recordPart: `collect(DISTINCT record {${INCLUDE_OWN_PROPERTIES}, ${label()}}) AS records`
155187
}
156188
}
157189

@@ -242,7 +274,7 @@ export function buildCollectFunction(
242274
}
243275

244276
return `${apocSortMapsArray(
245-
`collect(${uniq}${alias}${propertyName} {.*, ${label(alias)}})`,
277+
`collect(${uniq}${alias}${propertyName} {${INCLUDE_OWN_PROPERTIES}, ${label(alias)}})`,
246278
instruction.orderBy
247279
)}[${skip}..${limit}] AS \`${returnAlias}\``
248280
}
@@ -290,7 +322,7 @@ export function parseBottomUpQuery(
290322
}
291323

292324
const collectPart = `collect(DISTINCT ${currentRecord} {${[
293-
'.*',
325+
INCLUDE_OWN_PROPERTIES,
294326
...collectParts.map((variable) => `\`${variable}\``),
295327
label(currentRecord)
296328
].join(', ')}})`

0 commit comments

Comments
 (0)