-sub smsg_from_doc ($) {
- my ($doc) = @_;
- my $data = $doc->get_data or return;
- my $smsg = bless {}, 'PublicInbox::Smsg';
- $smsg->{ts} = int_val($doc, PublicInbox::Search::TS());
- my $dt = int_val($doc, PublicInbox::Search::DT());
- my ($yyyy, $mon, $dd, $hh, $mm, $ss) = unpack('A4A2A2A2A2A2', $dt);
- $smsg->{ds} = timegm($ss, $mm, $hh, $dd, $mon - 1, $yyyy);
- $smsg->load_from_data($data);
- $smsg;
+sub set_vmd {
+ my ($self, $docid, $vmd) = @_;
+ begin_txn_lazy($self);
+ my $doc = _get_doc($self, $docid) or return;
+ my ($end, @rm, @add);
+ my @x = @VMD_MAP;
+ while (my ($field, $pfx) = splice(@x, 0, 2)) {
+ my $set = $vmd->{$field} // next;
+ my %keep = map { $_ => 1 } @$set;
+ my %add = %keep;
+ $end //= $doc->termlist_end;
+ for (my $cur = $doc->termlist_begin; $cur != $end; $cur++) {
+ $cur->skip_to($pfx);
+ last if $cur == $end;
+ my $v = $cur->get_termname;
+ $v =~ s/\A$pfx//s or next;
+ $keep{$v} ? delete($add{$v}) : push(@rm, $pfx.$v);
+ }
+ push(@add, map { $pfx.$_ } keys %add);
+ }
+ return unless scalar(@rm) || scalar(@add);
+ $doc->remove_term($_) for @rm;
+ $doc->add_boolean_term($_) for @add;
+ $self->{xdb}->replace_document($docid, $doc);
+}
+
+sub apply_vmd_mod ($$) {
+ my ($doc, $vmd_mod) = @_;
+ my $updated = 0;
+ my @x = @VMD_MAP;
+ while (my ($field, $pfx) = splice(@x, 0, 2)) {
+ # field: "L" or "kw"
+ for my $val (@{$vmd_mod->{"-$field"} // []}) {
+ eval {
+ $doc->remove_term($pfx . $val);
+ ++$updated;
+ };
+ }
+ for my $val (@{$vmd_mod->{"+$field"} // []}) {
+ $doc->add_boolean_term($pfx . $val);
+ ++$updated;
+ }
+ }
+ $updated;
+}
+
+sub add_vmd {
+ my ($self, $docid, $vmd) = @_;
+ begin_txn_lazy($self);
+ my $doc = _get_doc($self, $docid) or return;
+ my @x = @VMD_MAP;
+ my $updated = 0;
+ while (my ($field, $pfx) = splice(@x, 0, 2)) {
+ my $add = $vmd->{$field} // next;
+ $doc->add_boolean_term($pfx . $_) for @$add;
+ $updated += scalar(@$add);
+ }
+ $updated += apply_vmd_mod($doc, $vmd);
+ $self->{xdb}->replace_document($docid, $doc) if $updated;
+}
+
+sub remove_vmd {
+ my ($self, $docid, $vmd) = @_;
+ begin_txn_lazy($self);
+ my $doc = _get_doc($self, $docid) or return;
+ my $replace;
+ my @x = @VMD_MAP;
+ while (my ($field, $pfx) = splice(@x, 0, 2)) {
+ my $rm = $vmd->{$field} // next;
+ for (@$rm) {
+ eval {
+ $doc->remove_term($pfx . $_);
+ $replace = 1;
+ };
+ }
+ }
+ $self->{xdb}->replace_document($docid, $doc) if $replace;
+}
+
+sub update_vmd {
+ my ($self, $docid, $vmd_mod) = @_;
+ begin_txn_lazy($self);
+ my $doc = _get_doc($self, $docid) or return;
+ my $updated = apply_vmd_mod($doc, $vmd_mod);
+ $self->{xdb}->replace_document($docid, $doc) if $updated;
+ $updated;