X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FSearch.pm;h=b6141f684ebc61699d6fcf63fe80d2f47fbe15a8;hb=76c3711c58682c40ca57d83406d31cb9cf4e7d09;hp=fbcff2c38fff410ab81f399901e14af3b55841c4;hpb=fd3b46aa0577d62553f61054270db6626ccc6759;p=public-inbox.git diff --git a/lib/PublicInbox/Search.pm b/lib/PublicInbox/Search.pm index fbcff2c3..b6141f68 100644 --- a/lib/PublicInbox/Search.pm +++ b/lib/PublicInbox/Search.pm @@ -1,20 +1,22 @@ -# Copyright (C) 2015-2021 all contributors +# Copyright (C) all contributors # License: AGPL-3.0+ # based on notmuch, but with no concept of folders, files or flags # # Read-only search interface for use by the web and NNTP interfaces package PublicInbox::Search; use strict; +use v5.10.1; use parent qw(Exporter); our @EXPORT_OK = qw(retry_reopen int_val get_pct xap_terms); use List::Util qw(max); use POSIX qw(strftime); +use Carp (); # values for searching, changing the numeric value breaks # compatibility with old indices (so don't change them it) use constant { TS => 0, # Received: in Unix time (IMAP INTERNALDATE, JMAP receivedAt) - YYYYMMDD => 1, # Date: header for searching in the WWW UI + YYYYMMDD => 1, # redundant with DT below DT => 2, # Date: YYYYMMDDHHMMSS (IMAP SENT*, JMAP sentAt) # added for public-inbox 1.6.0+ @@ -116,9 +118,10 @@ my %bool_pfx_external = ( dfpre => 'XDFPRE', dfpost => 'XDFPOST', dfblob => 'XDFPRE XDFPOST', + patchid => 'XDFID', ); -my $non_quoted_body = 'XNQ XDFN XDFA XDFB XDFHH XDFCTX XDFPRE XDFPOST'; +my $non_quoted_body = 'XNQ XDFN XDFA XDFB XDFHH XDFCTX XDFPRE XDFPOST XDFID'; my %prob_prefix = ( # for mairix compatibility s => 'S', @@ -153,12 +156,9 @@ my %prob_prefix = ( our @HELP = ( 's:' => 'match within Subject e.g. s:"a quick brown fox"', 'd:' => < < 'match within message body, including text attachments', 'nq:' => 'match non-quoted text within message body', @@ -179,6 +179,10 @@ EOF 'dfpre:' => 'match pre-image git blob ID', 'dfpost:' => 'match post-image git blob ID', 'dfblob:' => 'match either pre or post-image git blob ID', + 'patchid:' => "match `git patch-id --stable' output", + 'rt:' => <{shard})) { $self->{xpfx}; - } else { # v2 only: + } else { # v2 + extindex only: "$self->{xpfx}/$self->{shard}"; } } @@ -198,7 +202,7 @@ sub xdb_shards_flat ($) { my (@xdb, $slow_phrase); load_xapian(); $self->{qp_flags} //= $QP_FLAGS; - if ($xpfx =~ m/xapian${\SCHEMA_VERSION}\z/) { + if ($xpfx =~ m!/xapian[0-9]+\z!) { @xdb = ($X{Database}->new($xpfx)); $self->{qp_flags} |= FLAG_PHRASE() if !-f "$xpfx/iamchert"; } else { @@ -233,12 +237,12 @@ sub mset_to_artnums { sub xdb ($) { my ($self) = @_; - $self->{xdb} //= do { + $self->{xdb} // do { my @xdb = $self->xdb_shards_flat or return; $self->{nshard} = scalar(@xdb); my $xdb = shift @xdb; $xdb->add_database($_) for @xdb; - $xdb; + $self->{xdb} = $xdb; }; } @@ -246,10 +250,10 @@ sub new { my ($class, $ibx) = @_; ref $ibx or die "BUG: expected PublicInbox::Inbox object: $ibx"; my $xap = $ibx->version > 1 ? 'xap' : 'public-inbox/xapian'; - bless { - xpfx => "$ibx->{inboxdir}/$xap" . SCHEMA_VERSION, - altid => $ibx->{altid}, - }, $class; + my $xpfx = "$ibx->{inboxdir}/$xap".SCHEMA_VERSION; + my $self = bless { xpfx => $xpfx }, $class; + $self->{altid} = $ibx->{altid} if defined($ibx->{altid}); + $self; } sub reopen { @@ -331,7 +335,7 @@ sub date_parse_prepare { push @$to_parse, $x; $x = "\0%s$#$to_parse\0"; } - $r[1] //= "\0%s+\0"; + $r[1] //= "\0%s+\0"; # add 1 day } "$pfx:".join('..', @r).$end; } @@ -341,9 +345,12 @@ sub date_parse_finalize { # git-rev-parse can handle any number of args up to system # limits (around (4096*32) bytes on Linux). my @r = $git->date_parse(@$to_parse); - my $i; - $_[2] =~ s/\0(%[%YmdHMSs]+)([0-9\+]+)\0/strftime($1, - gmtime($2 eq '+' ? ($r[$i]+86400) : $r[$i=$2+0]))/sge; + # n.b. git respects TZ, times stored in SQLite/Xapian are always UTC, + # and gmtime doesn't seem to do the right thing when TZ!=UTC + my ($i, $t); + $_[2] =~ s/\0(%[%YmdHMSs]+)([0-9\+]+)\0/ + $t = $2 eq '+' ? ($r[$i]+86400) : $r[$i=$2+0]; + $1 eq '%s' ? $t : strftime($1, gmtime($t))/sge; } # n.b. argv never has NUL, though we'll need to filter it out @@ -394,27 +401,24 @@ sub retry_reopen { my ($self, $cb, @arg) = @_; for my $i (1..10) { if (wantarray) { - my @ret; - eval { @ret = $cb->($self, @arg) }; + my @ret = eval { $cb->($self, @arg) }; return @ret unless $@; } else { - my $ret; - eval { $ret = $cb->($self, @arg) }; + my $ret = eval { $cb->($self, @arg) }; return $ret unless $@; } # Exception: The revision being read has been discarded - # you should call Xapian::Database::reopen() if (ref($@) =~ /\bDatabaseModifiedError\b/) { - warn "reopen try #$i on $@\n"; reopen($self); } else { # let caller decide how to spew, because ExtMsg queries # get wonky and trigger: # "something terrible happened at .../Xapian/Enquire.pm" - die; + Carp::croak($@); } } - die "Too many Xapian database modifications in progress\n"; + Carp::croak("Too many Xapian database modifications in progress\n"); } sub _do_enquire { @@ -556,19 +560,23 @@ sub get_pct ($) { # mset item sub xap_terms ($$;@) { my ($pfx, $xdb_or_doc, @docid) = @_; # @docid may be empty () my %ret; - eval { - my $end = $xdb_or_doc->termlist_end(@docid); - my $cur = $xdb_or_doc->termlist_begin(@docid); - for (; $cur != $end; $cur++) { - $cur->skip_to($pfx); - last if $cur == $end; - my $tn = $cur->get_termname; - if (index($tn, $pfx) == 0) { - $ret{substr($tn, length($pfx))} = undef; - } - } - }; - \%ret; + my $end = $xdb_or_doc->termlist_end(@docid); + my $cur = $xdb_or_doc->termlist_begin(@docid); + for (; $cur != $end; $cur++) { + $cur->skip_to($pfx); + last if $cur == $end; + my $tn = $cur->get_termname; + $ret{substr($tn, length($pfx))} = undef if !index($tn, $pfx); + } + wantarray ? sort(keys(%ret)) : \%ret; +} + +# get combined docid from over.num: +# (not generic Xapian, only works with our sharding scheme) +sub num2docid ($$) { + my ($self, $num) = @_; + my $nshard = $self->{nshard}; + ($num - 1) * $nshard + $num % $nshard + 1; } 1;