Skip to content

Commit a5f0bb9

Browse files
authored
Merge pull request #456 from relaystr/fix/performance/objectbox-calc-id
fix(perf): remove calcId from db obj
2 parents 2b3f62b + 51a4cda commit a5f0bb9

File tree

5 files changed

+528
-642
lines changed

5 files changed

+528
-642
lines changed

packages/objectbox/lib/data_layer/db/object_box/db_object_box.dart

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)