| # Copyright (C) all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| |
| # Provides everything the PublicInbox::Search object does; |
| # but uses global ExtSearch (->ALL) with an eidx_key query to |
| # emulate per-Inbox search using ->ALL. |
| package PublicInbox::Isearch; |
| use v5.12; |
| use PublicInbox::ExtSearch; |
| use PublicInbox::Search; |
| |
| sub new { |
| my (undef, $ibx, $es) = @_; |
| my $self = bless { es => $es, eidx_key => $ibx->eidx_key }, __PACKAGE__; |
| # load publicinbox.*.{altid,indexheader} |
| PublicInbox::Search::load_extra_indexers($self, $ibx); |
| push @{$self->{-extra}}, @{$es->{-extra} // []} if $self->{-extra}; |
| $self; |
| } |
| |
| sub _ibx_id ($) { |
| my ($self) = @_; |
| my $sth = $self->{es}->over->dbh->prepare_cached(<<'', undef, 1); |
| SELECT ibx_id FROM inboxes WHERE eidx_key = ? LIMIT 1 |
| |
| $sth->execute($self->{eidx_key}); |
| $sth->fetchrow_array // |
| die "E: `$self->{eidx_key}' not in $self->{es}->{topdir}\n"; |
| } |
| |
| sub query_approxidate { $_[0]->{es}->query_approxidate($_[1], $_[2]) } |
| |
| sub eidx_mset_prep ($$) { |
| my ($self, $opt) = @_; |
| my %opt = $opt ? %$opt : (); |
| $opt{eidx_key} = $self->{eidx_key}; |
| my $uid_range = $opt{uid_range} or return \%opt; |
| my ($beg, $end) = @$uid_range; |
| my $ibx_id = $self->{-ibx_id} //= _ibx_id($self); |
| my $dbh = $self->{es}->over->dbh; |
| my $sth = $dbh->prepare_cached(<<'', undef, 1); |
| SELECT MIN(docid) FROM xref3 WHERE ibx_id = ? AND xnum >= ? AND xnum <= ? |
| |
| $sth->execute($ibx_id, $beg, $end); |
| my @r = ($sth->fetchrow_array); |
| |
| $sth = $dbh->prepare_cached(<<'', undef, 1); |
| SELECT MAX(docid) FROM xref3 WHERE ibx_id = ? AND xnum >= ? AND xnum <= ? |
| |
| $sth->execute($ibx_id, $beg, $end); |
| $r[1] = $sth->fetchrow_array; |
| if (defined($r[1]) && defined($r[0])) { |
| $opt{limit} = $r[1] - $r[0] + 1; |
| } else { |
| $r[1] //= $self->{es}->xdb->get_lastdocid; |
| $r[0] //= 0; |
| } |
| $opt{uid_range} = \@r; # these are fed to Xapian and SQLite |
| \%opt; |
| } |
| |
| sub _isrch_qparse ($) { |
| my ($self) = @_; |
| local $self->{es}->{-extra} = $self->{-extra}; |
| $self->{es}->qparse_new; # XXX worth memoizing? |
| } |
| |
| sub mset { |
| my ($self, $str, $opt) = @_; |
| local $self->{es}->{qp} = _isrch_qparse($self) if $self->{-extra}; |
| $self->{es}->mset($str, eidx_mset_prep $self, $opt); |
| } |
| |
| sub async_mset { |
| my ($self, $str, $opt, $cb, @args) = @_; |
| $opt = eidx_mset_prep $self, $opt; |
| local $self->{es}->{-extra} = $self->{-extra} if $self->{-extra}; |
| $self->{es}->async_mset($str, $opt, $cb, @args); |
| } |
| |
| sub mset_to_artnums { |
| my ($self, $mset, $opt) = @_; |
| my $docids = PublicInbox::Search::mset_to_artnums($self->{es}, $mset); |
| my $ibx_id = $self->{-ibx_id} //= _ibx_id($self); |
| my $qmarks = join(',', map { '?' } @$docids); |
| if ($opt && ($opt->{relevance} // 0) == -1) { # -1 => ENQ_ASCENDING |
| my $range = ''; |
| my @r; |
| if (my $r = $opt->{uid_range}) { |
| $range = 'AND xnum >= ? AND xnum <= ?'; |
| @r = @$r; |
| } |
| return $self->{es}->over->dbh-> |
| selectcol_arrayref(<<"", undef, $ibx_id, @$docids, @r); |
| SELECT xnum FROM xref3 WHERE ibx_id = ? AND docid IN ($qmarks) $range |
| ORDER BY xnum ASC |
| |
| } |
| |
| my $rows = $self->{es}->over->dbh-> |
| selectall_arrayref(<<"", undef, $ibx_id, @$docids); |
| SELECT docid,xnum FROM xref3 WHERE ibx_id = ? AND docid IN ($qmarks) |
| |
| my $i = -1; |
| my %order = map { $_ => ++$i } @$docids; |
| my @xnums; |
| for my $row (@$rows) { # @row = ($docid, $xnum) |
| my $idx = delete($order{$row->[0]}) // next; |
| $xnums[$idx] = $row->[1]; |
| } |
| if (scalar keys %order) { |
| warn "W: $self->{es}->{topdir} #", |
| join(', ', sort { $a <=> $b } keys %order), |
| " not mapped to `$self->{eidx_key}'\n"; |
| warn "W: $self->{es}->{topdir} may need to be reindexed\n"; |
| @xnums = grep { defined } @xnums; |
| } |
| \@xnums; |
| } |
| |
| sub mset_to_smsg { |
| my ($self, $ibx, $mset) = @_; # $ibx is a real inbox, not eidx |
| my $xnums = mset_to_artnums($self, $mset); |
| my $i = -1; |
| my %order = map { $_ => ++$i } @$xnums; |
| my $unordered = $ibx->over->get_all(@$xnums); |
| my @msgs; |
| for my $smsg (@$unordered) { |
| my $idx = delete($order{$smsg->{num}}) // do { |
| warn "W: $ibx->{inboxdir} #$smsg->{num}\n"; |
| next; |
| }; |
| $msgs[$idx] = $smsg; |
| } |
| if (scalar keys %order) { |
| warn "W: $ibx->{inboxdir} #", |
| join(', ', sort { $a <=> $b } keys %order), |
| " no longer valid\n"; |
| warn "W: $self->{es}->{topdir} may need to be reindexed\n"; |
| } |
| wantarray ? ($mset->get_matches_estimated, \@msgs) : \@msgs; |
| } |
| |
| sub has_threadid { 1 } |
| |
| sub help_txt { $_[0]->{es}->help_txt } |
| |
| sub xh_args { # prep getopt args to feed to xap_helper.h socket |
| my ($self, $opt) = @_; # TODO uid_range |
| ($self->{es}->xh_args, '-O', $self->{eidx_key}); |
| } |
| |
| 1; |