]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/Isearch.pm
7ca2f9e414eac3e63a60e8ef8bdf03a54ef5ccd9
[public-inbox.git] / lib / PublicInbox / Isearch.pm
1 # Copyright (C) 2020 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
4 # Provides everything the PublicInbox::Search object does;
5 # but uses global ExtSearch (->ALL) with an eidx_key query to
6 # emulate per-Inbox search using ->ALL.
7 package PublicInbox::Isearch;
8 use strict;
9 use v5.10.1;
10 use PublicInbox::ExtSearch;
11 use PublicInbox::Search;
12
13 sub new {
14         my (undef, $ibx, $es) = @_;
15         bless { es => $es, eidx_key => $ibx->eidx_key }, __PACKAGE__;
16 }
17
18 sub _ibx_id ($) {
19         my ($self) = @_;
20         my $sth = $self->{es}->over->dbh->prepare_cached(<<'', undef, 1);
21 SELECT ibx_id FROM inboxes WHERE eidx_key = ? LIMIT 1
22
23         $sth->execute($self->{eidx_key});
24         $sth->fetchrow_array //
25                 die "E: `$self->{eidx_key}' not in $self->{es}->{topdir}\n";
26 }
27
28
29 sub mset {
30         my ($self, $str, $opt) = @_;
31         my %opt = $opt ? %$opt : ();
32         $opt{eidx_key} = $self->{eidx_key};
33         if (my $uid_range = $opt{uid_range}) {
34                 my ($beg, $end) = @$uid_range;
35                 my $ibx_id = $self->{-ibx_id} //= _ibx_id($self);
36                 my $dbh = $self->{es}->{over}->dbh;
37                 my $sth = $dbh->prepare_cached(<<'', undef, 1);
38 SELECT MIN(docid) FROM xref3 WHERE ibx_id = ? AND xnum >= ? AND xnum <= ?
39
40                 $sth->execute($ibx_id, $beg, $end);
41                 my @r = ($sth->fetchrow_array);
42
43                 $sth = $dbh->prepare_cached(<<'', undef, 1);
44 SELECT MAX(docid) FROM xref3 WHERE ibx_id = ? AND xnum >= ? AND xnum <= ?
45
46                 $sth->execute($ibx_id, $beg, $end);
47                 $r[1] = $sth->fetchrow_array;
48                 if (defined($r[1]) && defined($r[0])) {
49                         $opt{limit} = $r[1] - $r[0] + 1;
50                 } else {
51                         $r[1] //= 0xffffffff;
52                         $r[0] //= 0;
53                 }
54                 $opt{uid_range} = \@r;
55         }
56         $self->{es}->mset($str, \%opt);
57 }
58
59 sub mset_to_artnums {
60         my ($self, $mset, $opt) = @_;
61         my $docids = PublicInbox::Search::mset_to_artnums($self->{es}, $mset);
62         my $ibx_id = $self->{-ibx_id} //= _ibx_id($self);
63         my $qmarks = join(',', map { '?' } @$docids);
64         if ($opt && ($opt->{relevance} // 0) == -1) { # -1 => ENQ_ASCENDING
65                 my $range = '';
66                 my @r;
67                 if (my $r = $opt->{uid_range}) {
68                         $range = 'AND xnum >= ? AND xnum <= ?';
69                         @r = @$r;
70                 }
71                 my $rows = $self->{es}->over->dbh->
72                         selectall_arrayref(<<"", undef, $ibx_id, @$docids, @r);
73 SELECT xnum FROM xref3 WHERE ibx_id = ? AND docid IN ($qmarks) $range
74 ORDER BY xnum ASC
75
76                 return [ map { $_->[0] } @$rows ];
77         }
78
79         my $rows = $self->{es}->over->dbh->
80                         selectall_arrayref(<<"", undef, $ibx_id, @$docids);
81 SELECT docid,xnum FROM xref3 WHERE ibx_id = ? AND docid IN ($qmarks)
82
83         my $i = -1;
84         my %order = map { $_ => ++$i } @$docids;
85         my @xnums;
86         for my $row (@$rows) { # @row = ($docid, $xnum)
87                 my $idx = delete($order{$row->[0]}) // next;
88                 $xnums[$idx] = $row->[1];
89         }
90         if (scalar keys %order) {
91                 warn "W: $self->{es}->{topdir} #",
92                         join(', ', sort { $a <=> $b } keys %order),
93                         " not mapped to `$self->{eidx_key}'\n";
94                 warn "W: $self->{es}->{topdir} may need to be reindexed\n";
95                 @xnums = grep { defined } @xnums;
96         }
97         \@xnums;
98 }
99
100 sub mset_to_smsg {
101         my ($self, $ibx, $mset) = @_; # $ibx is a real inbox, not eidx
102         my $xnums = mset_to_artnums($self, $mset);
103         my $i = -1;
104         my %order = map { $_ => ++$i } @$xnums;
105         my $unordered = $ibx->over->get_all(@$xnums);
106         my @msgs;
107         for my $smsg (@$unordered) {
108                 my $idx = delete($order{$smsg->{num}}) // do {
109                         warn "W: $ibx->{inboxdir} #$smsg->{num}\n";
110                         next;
111                 };
112                 $msgs[$idx] = $smsg;
113         }
114         if (scalar keys %order) {
115                 warn "W: $ibx->{inboxdir} #",
116                         join(', ', sort { $a <=> $b } keys %order),
117                         " no longer valid\n";
118                 warn "W: $self->{es}->{topdir} may need to be reindexed\n";
119         }
120         wantarray ? ($mset->get_matches_estimated, \@msgs) : \@msgs;
121 }
122
123 sub has_threadid { 1 }
124
125 sub help { $_[0]->{es}->help }
126
127 1;