@@ -155,8 +155,6 @@ export const buildQuery = ({
155155 const fallbackDir : OrderDirection = orderDirection || 'DESC'
156156 const { field : sortField , direction } = parseOrderBy ( orderBy , fallbackDir )
157157
158- console . log ( `filterString in buildQuery: ${ filterString } ` )
159-
160158 // Detect alias usage in filters (to decide joins/CTEs needed outside)
161159 const filterHasMo = filterString . includes ( 'mo.' )
162160 const filterHasMe = filterString . includes ( 'me.' )
@@ -178,18 +176,72 @@ export const buildQuery = ({
178176
179177 log . info ( `useDirectIdPath=${ useDirectIdPath } ` )
180178 if ( useDirectIdPath ) {
181- // ...existing direct path code...
179+ // Direct path: start from memberSegmentsAgg keyed by (memberId, segmentId)
180+ const ctes : string [ ] = [ ]
181+ if ( needsMemberOrgs ) ctes . push ( buildMemberOrgsCTE ( true ) . trim ( ) )
182+
183+ const withClause = ctes . length ? `WITH ${ ctes . join ( ',\n' ) } ` : ''
184+
185+ const memberOrgsJoin = needsMemberOrgs ? `LEFT JOIN member_orgs mo ON mo."memberId" = m.id` : ''
186+
187+ return `
188+ ${ withClause }
189+ SELECT ${ fields }
190+ FROM "memberSegmentsAgg" msa
191+ JOIN members m
192+ ON m.id = msa."memberId"
193+ ${ memberOrgsJoin }
194+ LEFT JOIN "memberEnrichments" me
195+ ON me."memberId" = m.id
196+ WHERE
197+ msa."segmentId" = $(segmentId)
198+ AND (${ filterString } )
199+ ORDER BY ${ orderClause } NULLS LAST
200+ LIMIT ${ limit }
201+ OFFSET ${ offset }
202+ ` . trim ( )
182203 }
183204
205+ // Check if filters are safe for activityCount optimization
206+ const hasSafeFilters = ( ( ) => {
207+ if ( ! filterString || filterString . trim ( ) === '' || filterString . match ( / ^ \s * 1 \s * = \s * 1 \s * $ / ) ) {
208+ return true // No filters or trivial filters
209+ }
210+
211+ // Allow filters that are likely to have good correlation with activity or are very selective
212+ const safePatterns = [
213+ / \b m \. i d \s * [ = I N ] / i, // ID filters are always safe
214+ / \( 1 \s * = \s * 1 \) / , // Trivial conditions
215+ / A N D \s * \( 1 \s * = \s * 1 \) / , // Trivial AND conditions
216+ / \( 1 \s * = \s * 1 \) \s * A N D / , // Trivial conditions at start
217+ ]
218+
219+ // Check if filter contains only safe patterns and basic logical operators
220+ let cleanFilter = filterString
221+ safePatterns . forEach ( ( pattern ) => {
222+ cleanFilter = cleanFilter . replace ( pattern , '' )
223+ } )
224+
225+ // Remove whitespace, parentheses, and basic logical operators
226+ cleanFilter = cleanFilter . replace ( / [ \s \( \) ] / g, '' ) . replace ( / \b ( a n d | o r ) \b / gi, '' )
227+
228+ // If nothing significant remains, it's safe
229+ return cleanFilter . length === 0
230+ } ) ( )
231+
184232 // Use activityCount optimization if:
185233 // 1. We have aggregates and are sorting by activityCount
186- // 2. No expensive joins needed (me.*, mo.* filters)
187- // 3. Only m.* filters (we can handle these in the CTE )
234+ // 2. No unsafe joins needed (me.*, mo.* filters)
235+ // 3. Filters are safe (mostly ID-based or trivial )
188236 const useActivityCountOptimized =
189- withAggregates && ( ! sortField || sortField === 'activityCount' ) && ! filterHasMe && ! filterHasMo
237+ withAggregates &&
238+ ( ! sortField || sortField === 'activityCount' ) &&
239+ ! filterHasMe &&
240+ ! filterHasMo &&
241+ hasSafeFilters
190242
191243 log . info (
192- `useActivityCountOptimized=${ useActivityCountOptimized } , filterHasMe=${ filterHasMe } , filterHasMo=${ filterHasMo } , filterHasM=${ filterHasM } ` ,
244+ `useActivityCountOptimized=${ useActivityCountOptimized } , filterHasMe=${ filterHasMe } , filterHasMo=${ filterHasMo } , filterHasM=${ filterHasM } , hasSafeFilters= ${ hasSafeFilters } ` ,
193245 )
194246
195247 if ( useActivityCountOptimized ) {
0 commit comments