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: header in Unix time (IMAP INTERNALDATE)
+ TS => 0, # Received: in Unix time (IMAP INTERNALDATE, JMAP receivedAt)
YYYYMMDD => 1, # Date: header for searching in the WWW UI
- DT => 2, # Date: YYYYMMDDHHMMSS
+ DT => 2, # Date: YYYYMMDDHHMMSS (IMAP SENT*, JMAP sentAt)
# added for public-inbox 1.6.0+
BYTES => 3, # IMAP RFC822.SIZE
my ($self, $rdonly) = @_;
if ($rdonly || !defined($self->{shard})) {
$self->{xpfx};
- } else { # v2 only:
+ } else { # v2 + extindex only:
"$self->{xpfx}/$self->{shard}";
}
}
# expand "d:20101002" => "d:20101002..20101003" and like
# n.b. git doesn't do YYYYMMDD w/o '-', it needs YYYY-MM-DD
+ # We upgrade "d:" to "dt:" to iff using approxidate
if ($pfx eq 'd') {
+ my $fmt = "\0%Y%m%d";
if (!defined($r[1])) {
if ($r[0] =~ /\A([0-9]{4})([0-9]{2})([0-9]{2})\z/) {
push @$to_parse, "$1-$2-$3";
# to parse anyways for "d+" below
} else {
push @$to_parse, $r[0];
+ if ($r[0] !~ /\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/) {
+ $pfx = 'dt';
+ $fmt = "\0%Y%m%d%H%M%S";
+ }
}
- $r[0] = "\0%Y%m%d$#$to_parse\0";
- $r[1] = "\0%Y%m%d+\0";
+ $r[0] = "$fmt+$#$to_parse\0";
+ $r[1] = "$fmt+\0";
} else {
for my $x (@r) {
next if $x eq '' || $x =~ /\A[0-9]{8}\z/;
push @$to_parse, $x;
- $x = "\0%Y%m%d$#$to_parse\0";
+ if ($x !~ /\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/) {
+ $pfx = 'dt';
+ }
+ $x = "$fmt$#$to_parse\0";
+ }
+ if ($pfx eq 'dt') {
+ for (@r) {
+ s/\0%Y%m%d/\0%Y%m%d%H%M%S/;
+ s/\A([0-9]{8})\z/${1}000000/;
+ }
}
}
} elsif ($pfx eq 'dt') {
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;
}
+sub date_parse_finalize {
+ my ($git, $to_parse) = @_;
+ # 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);
+ # 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
# if this $argv isn't from a command execution
sub query_argv_to_string {
my (undef, $git, $argv) = @_;
my $to_parse;
my $tmp = join(' ', map {;
- if (s!\b(d|rt|dt):([[:print:]]+)\z!date_parse_prepare(
+ if (s!\b(d|rt|dt):(\S+)\z!date_parse_prepare(
$to_parse //= [], $1, $2)!sge) {
$_;
+ } elsif (/\s/) {
+ s/(.*?)\b(\w+:)// ? qq{$1$2"$_"} : qq{"$_"};
} else {
- /\s/ ? (s/\A(\w+:)// ? qq{$1"$_"} : qq{"$_}) : $_
+ $_
}
} @$argv);
- # git-rev-parse can handle any number of args up to system
- # limits (around (4096*32) bytes on Linux).
- if ($to_parse) {
- my @r = $git->date_parse(@$to_parse);
- my $i;
- $tmp =~ s/\0(%[%YmdHMSs]+)([0-9\+]+)\0/strftime($1,
- gmtime($2 eq '+' ? ($r[$i]+86400) : $r[$i=$2+0]))/sge;
- }
+ date_parse_finalize($git, $to_parse, $tmp) if $to_parse;
$tmp
}
+# this is for the WWW "q=" query parameter and "lei q --stdin"
+# it can't do d:"5 days ago", but it will do d:5.days.ago
+sub query_approxidate {
+ my (undef, $git) = @_; # $_[2] = $query_string (modified in-place)
+ my $DQ = qq<"\x{201c}\x{201d}>; # Xapian can use curly quotes
+ $_[2] =~ tr/\x00/ /; # Xapian doesn't do NUL, we use it as a placeholder
+ my ($terms, $phrase, $to_parse);
+ $_[2] =~ s{([^$DQ]*)([$DQ][^$DQ]*[$DQ])?}{
+ ($terms, $phrase) = ($1, $2);
+ $terms =~ s!\b(d|rt|dt):(\S+)!
+ date_parse_prepare($to_parse //= [], $1, $2)!sge;
+ $terms.($phrase // '');
+ }sge;
+ date_parse_finalize($git, $to_parse, $_[2]) if $to_parse;
+}
+
# read-only
sub mset {
my ($self, $query_string, $opts) = @_;
$opts ||= {};
- my $qp = $self->{qp} //= qparse_new($self);
+ my $qp = $self->{qp} //= $self->qparse_new;
my $query = $qp->parse_query($query_string, $self->{qp_flags});
_do_enquire($self, $query, $opts);
}
# 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 {
my $enquire = $X{Enquire}->new($xdb);
$enquire->set_query($query);
$opts ||= {};
- my $desc = !$opts->{asc};
my $rel = $opts->{relevance} // 0;
- if ($rel == -1) { # ORDER BY docid/UID
+ if ($rel == -2) { # ORDER BY docid/UID (highest first)
+ $enquire->set_weighting_scheme($X{BoolWeight}->new);
+ $enquire->set_docid_order($ENQ_DESCENDING);
+ } elsif ($rel == -1) { # ORDER BY docid/UID (lowest first)
$enquire->set_weighting_scheme($X{BoolWeight}->new);
$enquire->set_docid_order($ENQ_ASCENDING);
} elsif ($rel == 0) {
- $enquire->set_sort_by_value_then_relevance(TS, $desc);
- } elsif ($rel == -2) {
- $enquire->set_weighting_scheme($X{BoolWeight}->new);
- $enquire->set_docid_order($ENQ_DESCENDING);
+ $enquire->set_sort_by_value_then_relevance(TS, !$opts->{asc});
} else { # rel > 0
- $enquire->set_sort_by_relevance_then_value(TS, $desc);
+ $enquire->set_sort_by_relevance_then_value(TS, !$opts->{asc});
}
# `mairix -t / --threads' or JMAP collapseThreads
sub stemmer { $X{Stem}->new($LANG) }
# read-only
-sub qparse_new ($) {
+sub qparse_new {
my ($self) = @_;
my $xdb = xdb($self);
$cb->($qp, $NVRP->new(DT, 'dt:'));
# for IMAP, undocumented for WWW and may be split off go away
- $cb->($qp, $NVRP->new(BYTES, 'bytes:'));
+ $cb->($qp, $NVRP->new(BYTES, 'z:'));
$cb->($qp, $NVRP->new(TS, 'rt:'));
$cb->($qp, $NVRP->new(UID, 'uid:'));
sub help {
my ($self) = @_;
- $self->{qp} //= qparse_new($self); # parse altids
+ $self->{qp} //= $self->qparse_new; # parse altids
my @ret = @HELP;
if (my $user_pfx = $self->{-user_pfx}) {
push @ret, @$user_pfx;
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;
}
1;