+sub PARTIAL_MAX () { 100 }
+
+sub search_partial ($$) {
+ my ($srch, $mid) = @_;
+ my $opt = { limit => PARTIAL_MAX, mset => 2 };
+ my @try = ("m:$mid*");
+ my $chop = $mid;
+ if ($chop =~ s/(\W+)(\w*)\z//) {
+ my ($delim, $word) = ($1, $2);
+ if (length($word)) {
+ push @try, "m:$chop$delim";
+ push @try, "m:$chop$delim*";
+ }
+ push @try, "m:$chop";
+ push @try, "m:$chop*";
+ }
+
+ # break out long words individually to search for, because
+ # too many messages begin with "Pine.LNX." (or "alpine" or "nycvar")
+ if ($mid =~ /\w{9,}/) {
+ my @long = ($mid =~ m!(\w{3,})!g);
+ push(@try, join(' ', map { "m:$_" } @long));
+
+ # is the last element long enough to not trigger excessive
+ # wildcard matches?
+ if (length($long[-1]) > 8) {
+ $long[-1] .= '*';
+ push(@try, join(' ', map { "m:$_" } @long));
+ }
+ }
+
+ foreach my $m (@try) {
+ my $mset = eval { $srch->query($m, $opt) };
+ if (ref($@) eq 'Search::Xapian::QueryParserError') {
+ # If Xapian can't handle the wildcard since it
+ # has too many results.
+ next;
+ }
+ my @mids = map {
+ my $doc = $_->get_document;
+ PublicInbox::SearchMsg->load_doc($doc)->mid;
+ } $mset->items;
+ return \@mids if scalar(@mids);
+ }
+}
+