# Copyright (C) 2020-2021 all contributors
# License: AGPL-3.0+
# 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 strict;
use v5.10.1;
use PublicInbox::ExtSearch;
use PublicInbox::Search;
sub new {
my (undef, $ibx, $es) = @_;
bless { es => $es, eidx_key => $ibx->eidx_key }, __PACKAGE__;
}
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 mset {
my ($self, $str, $opt) = @_;
my %opt = $opt ? %$opt : ();
$opt{eidx_key} = $self->{eidx_key};
if (my $uid_range = $opt{uid_range}) {
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] //= 0xffffffff;
$r[0] //= 0;
}
$opt{uid_range} = \@r;
}
$self->{es}->mset($str, \%opt);
}
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;
}
my $rows = $self->{es}->over->dbh->
selectall_arrayref(<<"", undef, $ibx_id, @$docids, @r);
SELECT xnum FROM xref3 WHERE ibx_id = ? AND docid IN ($qmarks) $range
ORDER BY xnum ASC
return [ map { $_->[0] } @$rows ];
}
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 { $_[0]->{es}->help }
1;