Skip to content

Commit 682eef1

Browse files
authored
Allow searching and filtering on packages that are provided in the query (#8907)
1 parent 2a7b85b commit 682eef1

File tree

6 files changed

+53
-5
lines changed

6 files changed

+53
-5
lines changed

app/lib/search/mem_index.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class InMemoryPackageIndex {
207207
BitArray packages,
208208
IndexedScore<String> Function() scoreFn,
209209
) {
210+
_resetBitArray(packages, query.packages);
210211
final predicateFilterCount = _filterOnPredicates(query, packages);
211212
if (predicateFilterCount <= query.offset) {
212213
return PackageSearchResult.empty();
@@ -333,6 +334,21 @@ class InMemoryPackageIndex {
333334
);
334335
}
335336

337+
/// The [BitArrayPool] does not reset the reused pool items, because initialization
338+
/// depends on the presence of the [filterOnPackages] list.
339+
void _resetBitArray(BitArray selected, List<String>? filterOnPackages) {
340+
if (filterOnPackages != null && filterOnPackages.isNotEmpty) {
341+
selected.clearAll();
342+
for (final package in filterOnPackages) {
343+
final index = _nameToIndex[package];
344+
if (index == null) continue;
345+
selected.setBit(index);
346+
}
347+
} else {
348+
selected.setRange(0, _documents.length);
349+
}
350+
}
351+
336352
/// Returns the package name that is considered as the best name match
337353
/// for the [query], or `null` if there is no such package name, or the
338354
/// match is not enabled for the given context (e.g. non-default ordering).

app/lib/search/search_service.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ class ServiceSearchQuery {
211211
int get offset => max(0, _data.offset ?? 0);
212212
int get limit => max(_minSearchLimit, _data.limit ?? 10);
213213
TextMatchExtent? get textMatchExtent => _data.textMatchExtent;
214+
List<String>? get packages => _data.packages;
214215

215216
Map<String, dynamic> toUriQueryParameters() {
216217
return _data.toUriQueryParameters();

app/lib/search/token_index.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,8 @@ class ScorePool<K> extends _AllocationPool<IndexedScore<K>> {
249249
class BitArrayPool extends _AllocationPool<BitArray> {
250250
BitArrayPool(int length)
251251
: super(
252-
// sets all bits to 1
253-
() => BitArray(length)..setRange(0, length),
254-
// sets all bits to 1
255-
(array) => array.setRange(0, length),
252+
() => BitArray(length),
253+
(array) {}, // keeping the array as-is, reset happens at the beginning of the processing
256254
);
257255
}
258256

app/test/search/mem_index_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:convert';
66

77
import 'package:_pub_shared/search/search_form.dart';
8+
import 'package:_pub_shared/search/search_request_data.dart';
89
import 'package:clock/clock.dart';
910
import 'package:pub_dev/search/mem_index.dart';
1011
import 'package:pub_dev/search/search_service.dart';
@@ -543,6 +544,28 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure
543544
ServiceSearchQuery.parse(query: '=', order: SearchOrder.text));
544545
expect(rs.isEmpty, isTrue);
545546
});
547+
548+
test('only packages list filter', () {
549+
final rs = index.search(
550+
ServiceSearchQuery(SearchRequestData(packages: ['async', 'http'])));
551+
// returns two packages, ordered by overall score
552+
expect(rs.packageHits.map((e) => e.package).toList(), ['http', 'async']);
553+
});
554+
555+
test('query + packages list filter', () {
556+
// library itself would return `http` too
557+
final rs = index.search(ServiceSearchQuery(SearchRequestData(
558+
query: 'library',
559+
packages: ['async'],
560+
)));
561+
expect(rs.packageHits.map((e) => e.package).toList(), ['async']);
562+
});
563+
564+
test('non-existent package name', () {
565+
final rs = index.search(
566+
ServiceSearchQuery(SearchRequestData(packages: ['not-a-package'])));
567+
expect(rs.packageHits, isEmpty);
568+
});
546569
});
547570

548571
group('special cases', () {

pkg/_pub_shared/lib/search/search_request_data.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class SearchRequestData {
1717
final int? offset;
1818
final int? limit;
1919
final TextMatchExtent? textMatchExtent;
20+
final List<String>? packages;
2021

2122
SearchRequestData({
2223
String? query,
@@ -27,8 +28,10 @@ class SearchRequestData {
2728
this.offset,
2829
this.limit,
2930
this.textMatchExtent,
31+
List<String>? packages,
3032
}) : query = _trimToNull(query),
31-
publisherId = _trimToNull(publisherId);
33+
publisherId = _trimToNull(publisherId),
34+
packages = packages != null && packages.isNotEmpty ? packages : null;
3235

3336
factory SearchRequestData.fromJson(Map<String, dynamic> json) =>
3437
_$SearchRequestDataFromJson(json);
@@ -54,6 +57,7 @@ class SearchRequestData {
5457
break;
5558
}
5659
}
60+
final packages = uri.queryParametersAll['packages'];
5761

5862
return SearchRequestData(
5963
query: q,
@@ -64,6 +68,7 @@ class SearchRequestData {
6468
offset: offset,
6569
limit: limit,
6670
textMatchExtent: textMatchExtent,
71+
packages: packages,
6772
);
6873
}
6974

@@ -78,6 +83,7 @@ class SearchRequestData {
7883
'limit': (limit ?? 10).toString(),
7984
'order': order?.name,
8085
if (textMatchExtent != null) 'textMatchExtent': textMatchExtent!.name,
86+
if (packages != null && packages!.isNotEmpty) 'packages': packages,
8187
};
8288
map.removeWhere((k, v) => v == null);
8389
return map;

pkg/_pub_shared/lib/search/search_request_data.g.dart

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)