Skip to content

Commit 92e3cb0

Browse files
committed
fix: pages bug
1 parent 51bdaf6 commit 92e3cb0

File tree

1 file changed

+48
-36
lines changed

1 file changed

+48
-36
lines changed

services/libs/data-access-layer/src/members/queryBuilder.ts

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export const buildQuery = ({
158158
// Detect alias usage in filters (to decide joins/CTEs needed outside)
159159
const filterHasMo = filterString.includes('mo.')
160160
const filterHasMe = filterString.includes('me.')
161+
const filterHasM = filterString.includes('m.') && !filterString.match(/\bm\.id\b/)
161162
const needsMemberOrgs = includeMemberOrgs || filterHasMo
162163

163164
// If filters pin m.id to a single value or a small IN-list, skip top-N entirely.
@@ -199,61 +200,72 @@ export const buildQuery = ({
199200
`.trim()
200201
}
201202

202-
// Decide if we can use the activityCount-optimized path
203+
// Only use activityCount optimization if:
204+
// 1. We have aggregates and are sorting by activityCount
205+
// 2. No filters on member attributes, enrichments, or organizations (only segment/search filters are safe)
206+
// 3. Only basic filters that don't reduce the result set significantly
207+
const hasUnsafeFilters = filterHasMe || filterHasM || filterHasMo
203208
const useActivityCountOptimized =
204-
withAggregates && (!sortField || sortField === 'activityCount') && !filterHasMe
205-
// (we do allow mo.* now, but only outside the CTE; see below)
206-
log.info(`useActivityCountOptimized=${useActivityCountOptimized}`)
209+
withAggregates &&
210+
(!sortField || sortField === 'activityCount') &&
211+
!hasUnsafeFilters &&
212+
// Only allow if filterString is just basic segment/id filters or empty
213+
(!filterString || filterString.trim() === '' || filterString.match(/^\s*1\s*=\s*1\s*$/))
214+
215+
log.info(
216+
`useActivityCountOptimized=${useActivityCountOptimized}, hasUnsafeFilters=${hasUnsafeFilters}`,
217+
)
218+
207219
if (useActivityCountOptimized) {
208220
const ctes: string[] = []
209-
if (needsMemberOrgs) ctes.push(buildMemberOrgsCTE(true).trim())
210221
if (searchConfig.cte) ctes.push(searchConfig.cte.trim())
211222

212223
const searchJoinForTopMembers = searchConfig.cte
213224
? `\n INNER JOIN member_search ms ON ms."memberId" = msa."memberId"`
214225
: ''
215226

216-
const oversampleMultiplier = 5
217-
const prefetch = Math.max(offset + limit * oversampleMultiplier, offset + limit)
227+
// Fix pagination: fetch enough rows to handle the requested page correctly
228+
const totalNeeded = limit + offset
218229

219230
ctes.push(
220231
`
221-
top_members AS (
222-
SELECT
223-
msa."memberId",
224-
msa."activityCount",
225-
ROW_NUMBER() OVER (
226-
ORDER BY msa."activityCount" ${direction} NULLS LAST, msa."memberId" ASC
227-
) AS rn
228-
FROM "memberSegmentsAgg" msa
229-
${searchJoinForTopMembers}
230-
WHERE msa."segmentId" = $(segmentId)
231-
ORDER BY msa."activityCount" ${direction} NULLS LAST
232-
LIMIT ${prefetch}
233-
)
234-
`.trim(),
232+
top_members AS (
233+
SELECT
234+
msa."memberId",
235+
msa."activityCount"
236+
FROM "memberSegmentsAgg" msa
237+
${searchJoinForTopMembers}
238+
WHERE
239+
msa."segmentId" = $(segmentId)
240+
ORDER BY
241+
msa."activityCount" ${direction} NULLS LAST
242+
LIMIT ${totalNeeded}
243+
)
244+
`.trim(),
235245
)
236246

237247
const withClause = `WITH ${ctes.join(',\n')}`
238-
const memberOrgsJoin = needsMemberOrgs ? `LEFT JOIN member_orgs mo ON mo."memberId" = m.id` : ''
239248

240249
return `
241-
${withClause}
242-
SELECT ${fields}
243-
FROM top_members tm
244-
JOIN members m ON m.id = tm."memberId"
245-
INNER JOIN "memberSegmentsAgg" msa
246-
ON msa."memberId" = m.id AND msa."segmentId" = $(segmentId)
247-
${memberOrgsJoin}
248-
LEFT JOIN "memberEnrichments" me ON me."memberId" = m.id
249-
WHERE (${filterString})
250-
AND tm.rn > ${offset}
251-
ORDER BY tm.rn ASC
252-
LIMIT ${limit}
253-
`.trim()
250+
${withClause}
251+
SELECT ${fields}
252+
FROM top_members tm
253+
JOIN members m
254+
ON m.id = tm."memberId"
255+
INNER JOIN "memberSegmentsAgg" msa
256+
ON msa."memberId" = m.id
257+
AND msa."segmentId" = $(segmentId)
258+
LEFT JOIN "memberEnrichments" me
259+
ON me."memberId" = m.id
260+
WHERE (${filterString})
261+
ORDER BY
262+
msa."activityCount" ${direction} NULLS LAST
263+
LIMIT ${limit}
264+
OFFSET ${offset}
265+
`.trim()
254266
}
255267

256-
// Fallback path (other sorts / non-aggregate)
268+
// Fallback path (other sorts / non-aggregate / filtered queries)
257269
const baseCtes = [needsMemberOrgs ? buildMemberOrgsCTE(true) : '', searchConfig.cte].filter(
258270
Boolean,
259271
)

0 commit comments

Comments
 (0)