@@ -129,6 +129,17 @@ class DbObjectBox implements CacheManager {
129129 (condition == null ) ? untilCondition : condition.and (untilCondition);
130130 }
131131
132+ if (tags != null && tags.isNotEmpty) {
133+ final matchingEventIds = _findEventIdsByTags (eventBox, tags);
134+ if (matchingEventIds.isEmpty) {
135+ return [];
136+ }
137+
138+ final tagCondition = DbNip01Event_ .dbId.oneOf (matchingEventIds.toList ());
139+ condition =
140+ (condition == null ) ? tagCondition : condition.and (tagCondition);
141+ }
142+
132143 // Create and build the query
133144 QueryBuilder <DbNip01Event > queryBuilder;
134145 if (condition != null ) {
@@ -147,41 +158,7 @@ class DbObjectBox implements CacheManager {
147158 }
148159 final results = query.find ();
149160
150- // For tag filtering, we need to do it in memory since ObjectBox doesn't support
151- // complex JSON querying within arrays
152- List <DbNip01Event > filteredResults = results;
153-
154- // Apply tag filters in memory if needed
155- if (tags != null && tags.isNotEmpty) {
156- filteredResults = results.where ((event) {
157- // Check if the event matches all tag filters
158- return tags.entries.every ((tagEntry) {
159- String tagKey = tagEntry.key;
160- List <String > tagValues = tagEntry.value;
161-
162- // Handle the special case where tag key starts with '#'
163- if (tagKey.startsWith ('#' ) && tagKey.length > 1 ) {
164- tagKey = tagKey.substring (1 ); // Remove the '#' prefix
165- }
166-
167- // Get all tags with this key
168- List <DbTag > eventTags =
169- event.tags.where ((t) => t.key == tagKey).toList ();
170-
171- // Check if any of the event's tags with this key have a value in the requested values
172- return eventTags.any ((tag) =>
173- tagValues.contains (tag.value) ||
174- tagValues.contains (tag.value.toLowerCase ()));
175- });
176- }).toList ();
177- }
178-
179- // Apply limit after filtering if tags were applied
180- final limitedResults = (limit != null && limit > 0 && tags != null )
181- ? filteredResults.take (limit).toList ()
182- : filteredResults;
183-
184- return limitedResults.map ((dbEvent) => dbEvent.toNdk ()).toList ();
161+ return results.map ((dbEvent) => dbEvent.toNdk ()).toList ();
185162 }
186163
187164 @override
@@ -485,35 +462,73 @@ class DbObjectBox implements CacheManager {
485462 conditions.add (DbNip01Event_ .createdAt.lessOrEqual (until));
486463 }
487464
465+ if (tags != null && tags.isNotEmpty) {
466+ final matchingEventIds = _findEventIdsByTags (eventBox, tags);
467+ if (matchingEventIds.isEmpty) {
468+ return ;
469+ }
470+ conditions.add (DbNip01Event_ .dbId.oneOf (matchingEventIds.toList ()));
471+ }
472+
488473 // Build and execute the query
489474 final query = conditions.isEmpty
490475 ? eventBox.query ().build ()
491476 : eventBox.query (conditions.reduce ((a, b) => a.and (b))).build ();
492- var results = query.find ();
477+ final results = query.find ();
493478
494- // Apply tag filters in memory if needed
495- if (tags != null && tags.isNotEmpty) {
496- results = results.where ((event) {
497- return tags.entries.every ((tagEntry) {
498- String tagKey = tagEntry.key;
499- List <String > tagValues = tagEntry.value;
479+ // Remove matching events
480+ eventBox.removeMany (results.map ((e) => e.dbId).toList ());
481+ }
500482
501- if (tagKey.startsWith ('#' ) && tagKey.length > 1 ) {
502- tagKey = tagKey.substring (1 );
503- }
483+ /// Find event DB IDs matching all given tag filters.
484+ /// [tags] is a map of tag key -> list of acceptable values.
485+ /// Returns the intersection of matching event IDs across all tag keys.
486+ ///
487+ /// Usage (on the calling side):
488+ /// ```
489+ /// final eventBox = store.box<DbNip01Event>();
490+ /// final ids = DbNip01Event.findEventIdsByTags(eventBox, {"p": ["abc"], "t": ["nostr"]});
491+ /// ```
492+ static Set <int > _findEventIdsByTags (
493+ Box <DbNip01Event > eventBox,
494+ Map <String , List <String >> tags,
495+ ) {
496+ Set <int >? matchingEventIds;
497+
498+ for (final entry in tags.entries) {
499+ final key = entry.key.trim ().toLowerCase ();
500+ final values = entry.value
501+ .map ((v) => v.trim ().toLowerCase ())
502+ .where ((v) => v.isNotEmpty)
503+ .toList ();
504+
505+ if (key.isEmpty || values.isEmpty) {
506+ return < int > {};
507+ }
508+
509+ final indexValues = [for (final v in values) '$key :$v ' ];
504510
505- List <DbTag > eventTags =
506- event.tags.where ((t) => t.key == tagKey).toList ();
511+ Condition <DbNip01Event >? condition;
512+ for (final v in indexValues) {
513+ final c = DbNip01Event_ .tagsIndex.containsElement (v);
514+ condition = condition == null ? c : condition.or (c);
515+ }
516+ final query = eventBox.query (condition! ).build ();
517+ final eventIdsForTag = query.findIds ().toSet ();
518+ query.close ();
519+
520+ if (matchingEventIds == null ) {
521+ matchingEventIds = eventIdsForTag;
522+ } else {
523+ matchingEventIds = matchingEventIds.intersection (eventIdsForTag);
524+ }
507525
508- return eventTags.any ((tag) =>
509- tagValues.contains (tag.value) ||
510- tagValues.contains (tag.value.toLowerCase ()));
511- });
512- }).toList ();
526+ if (matchingEventIds.isEmpty) {
527+ return < int > {};
528+ }
513529 }
514530
515- // Remove matching events
516- eventBox.removeMany (results.map ((e) => e.dbId).toList ());
531+ return matchingEventIds ?? < int > {};
517532 }
518533
519534 @override
0 commit comments