diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index abf076c7..1265a98b 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -7,6 +7,7 @@ import { nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { DittoTables } from '@/db/DittoTables.ts'; +import { normalizeFilters } from '@/filter.ts'; import { dbEventCounter, dbQueryCounter } from '@/metrics.ts'; import { RelayError } from '@/RelayError.ts'; import { purifyEvent } from '@/storages/hydrate.ts'; @@ -250,7 +251,7 @@ class EventsDB implements NStore { /** Converts filters to more performant, simpler filters that are better for SQLite. */ async expandFilters(filters: NostrFilter[]): Promise { - filters = structuredClone(filters); + filters = normalizeFilters(structuredClone(filters)); for (const filter of filters) { if (filter.search) { @@ -287,10 +288,42 @@ class EventsDB implements NStore { // If this results in an empty kinds array, NDatabase will remove the filter before querying and return no results. filter.kinds = filter.kinds.filter((kind) => !NKinds.ephemeral(kind)); } + + // Convert tag filters into `ids` filter for performance reasons. + const tagEntries = Object.entries(filter).filter(EventsDB.isTagEntry); + if (tagEntries.length) { + const tagIds: string[] = await tagEntries + .map(([key, value]) => + this.kysely + .selectFrom('nostr_tags') + .select('event_id') + .distinct() + .where('name', '=', key.replace(/^#/, '')) + .where('value', 'in', value) + ) + .reduce((result, query) => result.intersect(query)) + .execute() + .then((rows) => rows.map(({ event_id }) => event_id)); + + if (tagIds.length) { + filter.ids = filter.ids ?? []; + filter.ids.push(...tagIds); + } + + for (const [key] of tagEntries) { + delete filter[key]; + } + } } return filters; } + + /** Check if the object entry is a valid tag filter. */ + private static isTagEntry(entry: [string, unknown]): entry is [`#${string}`, string[]] { + const [key, value] = entry; + return key.startsWith('#') && Array.isArray(value); + } } export { EventsDB };