@@ -168,6 +168,109 @@ export class EventRepository implements IEventRepository {
168168 return query
169169 }
170170
171+ public async countByFilters ( filters : SubscriptionFilter [ ] ) : Promise < number > {
172+ debug ( 'counting events for %o' , filters )
173+
174+ if ( ! Array . isArray ( filters ) || ! filters . length ) {
175+ throw new Error ( 'Filters cannot be empty' )
176+ }
177+
178+ const now = Math . floor ( Date . now ( ) / 1000 )
179+
180+ const queries = filters . map ( ( currentFilter ) => {
181+ const builder = this . readReplicaDbClient < DBEvent > ( 'events' ) . select ( 'events.event_id' )
182+
183+ forEachObjIndexed ( ( tableFields : string [ ] , filterName : string | number ) => {
184+ builder . andWhere ( ( bd ) => {
185+ cond ( [
186+ [ isEmpty , ( ) => void bd . whereRaw ( '1 = 0' ) ] ,
187+ [
188+ complement ( isNil ) ,
189+ pipe (
190+ groupByLengthSpec ,
191+ evolve ( {
192+ exact : ( pubkeys : string [ ] ) =>
193+ tableFields . forEach ( ( tableField ) => bd . orWhereIn ( tableField , pubkeys . map ( toBuffer ) ) ) ,
194+ even : forEach ( ( prefix : string ) =>
195+ tableFields . forEach ( ( tableField ) =>
196+ bd . orWhereRaw ( `substring("${ tableField } " from 1 for ?) = ?` , [ prefix . length >> 1 , toBuffer ( prefix ) ] ) ,
197+ ) ,
198+ ) ,
199+ odd : forEach ( ( prefix : string ) =>
200+ tableFields . forEach ( ( tableField ) =>
201+ bd . orWhereRaw ( `substring("${ tableField } " from 1 for ?) BETWEEN ? AND ?` , [
202+ ( prefix . length >> 1 ) + 1 ,
203+ `\\x${ prefix } 0` ,
204+ `\\x${ prefix } f` ,
205+ ] ) ,
206+ ) ,
207+ ) ,
208+ } as any ) ,
209+ ) ,
210+ ] ,
211+ ] ) ( currentFilter [ filterName ] as string [ ] )
212+ } )
213+ } ) ( { authors : [ 'event_pubkey' ] , ids : [ 'event_id' ] } )
214+
215+ if ( Array . isArray ( currentFilter . kinds ) ) {
216+ builder . whereIn ( 'event_kind' , currentFilter . kinds )
217+ }
218+
219+ if ( typeof currentFilter . since === 'number' ) {
220+ builder . where ( 'event_created_at' , '>=' , currentFilter . since )
221+ }
222+
223+ if ( typeof currentFilter . until === 'number' ) {
224+ builder . where ( 'event_created_at' , '<=' , currentFilter . until )
225+ }
226+
227+ if ( typeof currentFilter . limit === 'number' ) {
228+ builder . limit ( currentFilter . limit ) . orderBy ( 'event_created_at' , 'DESC' ) . orderBy ( 'event_id' , 'asc' )
229+ }
230+
231+ const andWhereRaw = invoker ( 1 , 'andWhereRaw' )
232+ const orWhereRaw = invoker ( 2 , 'orWhereRaw' )
233+
234+ let isTagQuery = false
235+ pipe (
236+ toPairs ,
237+ filter ( pipe ( nth ( 0 ) as ( ) => string , isGenericTagQuery ) ) as any ,
238+ forEach ( ( [ filterName , criteria ] : [ string , string [ ] ] ) => {
239+ isTagQuery = true
240+ builder . andWhere ( ( bd ) => {
241+ ifElse (
242+ isEmpty ,
243+ ( ) => andWhereRaw ( '1 = 0' , bd ) ,
244+ forEach (
245+ ( criterion : string ) =>
246+ void orWhereRaw ( 'event_tags.tag_name = ? AND event_tags.tag_value = ?' , [ filterName [ 1 ] , criterion ] , bd ) ,
247+ ) ,
248+ ) ( criteria )
249+ } )
250+ } ) ,
251+ ) ( currentFilter as any )
252+
253+ if ( isTagQuery ) {
254+ builder . leftJoin ( 'event_tags' , 'events.event_id' , 'event_tags.event_id' ) . select ( 'events.event_id' )
255+ }
256+
257+ builder . whereNull ( 'events.deleted_at' ) . andWhere ( ( bd ) => {
258+ bd . whereNull ( 'events.expires_at' ) . orWhere ( 'events.expires_at' , '>' , now )
259+ } )
260+
261+ return builder
262+ } )
263+
264+ const [ query , ...subqueries ] = queries
265+ if ( subqueries . length ) {
266+ query . union ( subqueries , true )
267+ }
268+
269+ const result = await this . readReplicaDbClient . from ( query . as ( 'matching_events' ) ) . countDistinct ( { count : 'event_id' } ) . first ( )
270+
271+ return Number ( result ?. count ?? 0 )
272+ }
273+
171274 public async create ( event : Event ) : Promise < number > {
172275 return this . insert ( event ) . then ( prop ( 'rowCount' ) as ( ) => number , ( ) => 0 )
173276 }
0 commit comments