| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Copyrights 2012-2022 by [Mark Overmeer]. | 
| 2 |  |  |  |  |  |  | #  For other contributors see ChangeLog. | 
| 3 |  |  |  |  |  |  | # See the manual pages for details on the licensing terms. | 
| 4 |  |  |  |  |  |  | # Pod stripped from pm file by OODoc 2.03. | 
| 5 |  |  |  |  |  |  | # This code is part of distribution Apache-Solr.  Meta-POD processed with | 
| 6 |  |  |  |  |  |  | # OODoc into POD and HTML manual-pages.  See README.md | 
| 7 |  |  |  |  |  |  | # Copyright Mark Overmeer.  Licensed under the same terms as Perl itself. | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | package Apache::Solr; | 
| 10 | 5 |  |  | 5 |  | 3768 | use vars '$VERSION'; | 
|  | 5 |  |  |  |  | 19 |  | 
|  | 5 |  |  |  |  | 231 |  | 
| 11 |  |  |  |  |  |  | $VERSION = '1.07'; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 5 |  |  | 5 |  | 25 | use warnings; | 
|  | 5 |  |  |  |  | 6 |  | 
|  | 5 |  |  |  |  | 117 |  | 
| 15 | 5 |  |  | 5 |  | 18 | use strict; | 
|  | 5 |  |  |  |  | 6 |  | 
|  | 5 |  |  |  |  | 75 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 5 |  |  | 5 |  | 1231 | use Apache::Solr::Tables; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 524 |  | 
| 18 | 5 |  |  | 5 |  | 2001 | use Log::Report    qw(solr); | 
|  | 5 |  |  |  |  | 475765 |  | 
|  | 5 |  |  |  |  | 25 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 5 |  |  | 5 |  | 1159 | use Scalar::Util   qw/blessed/; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 187 |  | 
| 21 | 5 |  |  | 5 |  | 22 | use Encode         qw/encode/; | 
|  | 5 |  |  |  |  | 8 |  | 
|  | 5 |  |  |  |  | 159 |  | 
| 22 | 5 |  |  | 5 |  | 27 | use Scalar::Util   qw/weaken/; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 163 |  | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 5 |  |  | 5 |  | 2159 | use URI            (); | 
|  | 5 |  |  |  |  | 18350 |  | 
|  | 5 |  |  |  |  | 92 |  | 
| 25 | 5 |  |  | 5 |  | 2779 | use LWP::UserAgent (); | 
|  | 5 |  |  |  |  | 160214 |  | 
|  | 5 |  |  |  |  | 111 |  | 
| 26 | 5 |  |  | 5 |  | 1832 | use MIME::Types    (); | 
|  | 5 |  |  |  |  | 17250 |  | 
|  | 5 |  |  |  |  | 110 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 5 |  |  | 5 |  | 37 | use constant LATEST_SOLR_VERSION => '4.5';  # newest support by this module | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 17802 |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | # overrule this when your host has a different unique field | 
| 31 |  |  |  |  |  |  | our $uniqueKey  = 'id'; | 
| 32 |  |  |  |  |  |  | my  $mimetypes  = MIME::Types->new; | 
| 33 |  |  |  |  |  |  | my  $http_agent; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | sub _to_bool($) | 
| 36 | 22 |  |  | 22 |  | 24 | {  my $b = shift; | 
| 37 | 22 | 100 | 66 |  |  | 131 | !defined $b ? undef | 
|  |  | 50 |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | : ($b && $b ne 'false' && $b ne 'off') ? 'true' | 
| 39 |  |  |  |  |  |  | : 'false'; | 
| 40 |  |  |  |  |  |  | } | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | sub new(@) | 
| 44 | 4 |  |  | 4 | 1 | 409 | {   my ($class, %args) = @_; | 
| 45 | 4 | 100 |  |  |  | 17 | if($class eq __PACKAGE__) | 
| 46 | 2 |  | 50 |  |  | 12 | {   my $format = delete $args{format} || 'XML'; | 
| 47 | 2 | 50 | 33 |  |  | 7 | $format eq 'XML' || $format eq 'JSON' | 
| 48 |  |  |  |  |  |  | or panic "unknown communication format '$format' for solr"; | 
| 49 | 2 |  |  |  |  | 6 | $class .= '::' . $format; | 
| 50 | 2 | 50 |  |  |  | 74 | eval "require $class"; panic $@ if $@; | 
|  | 2 |  |  |  |  | 16 |  | 
| 51 |  |  |  |  |  |  | } | 
| 52 | 4 |  |  |  |  | 25 | (bless {}, $class)->init(\%args) | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub init($) | 
| 56 | 4 |  |  | 4 | 0 | 10 | {   my ($self, $args) = @_; | 
| 57 | 4 |  |  |  |  | 23 | $self->server($args->{server}); | 
| 58 | 4 |  |  |  |  | 11 | $self->{AS_core}     = $args->{core}; | 
| 59 | 4 | 50 |  |  |  | 16 | $self->{AS_commit}   = exists $args->{autocommit} ? $args->{autocommit} : 1; | 
| 60 | 4 |  | 50 |  |  | 23 | $self->{AS_sversion} = $args->{server_version} || LATEST_SOLR_VERSION; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | $http_agent = $self->{AS_agent} = $args->{agent} || | 
| 63 | 4 |  | 33 |  |  | 49 | $http_agent || LWP::UserAgent->new(keep_alive=>1); | 
| 64 | 4 |  |  |  |  | 15430 | weaken $http_agent; | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 4 |  |  |  |  | 43 | $self; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | #--------------- | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 1 | 50 |  | 1 | 1 | 2 | sub core(;$) { my $s = shift; @_ ? $s->{AS_core}   = shift : $s->{AS_core} } | 
|  | 1 |  |  |  |  | 5 |  | 
| 72 |  |  |  |  |  |  | sub autocommit(;$) | 
| 73 | 0 | 0 |  | 0 | 1 | 0 | { my $s = shift; @_ ? $s->{AS_commit} = shift : $s->{AS_commit} } | 
|  | 0 |  |  |  |  | 0 |  | 
| 74 | 0 |  |  | 0 | 1 | 0 | sub agent()  {shift->{AS_agent}} | 
| 75 | 26 |  |  | 26 | 1 | 36 | sub serverVersion() {shift->{AS_sversion}} | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | sub server(;$) | 
| 79 | 5 |  |  | 5 | 1 | 10 | {   my ($self, $uri) = @_; | 
| 80 | 5 | 100 |  |  |  | 21 | $uri or return $self->{AS_server}; | 
| 81 | 4 | 50 | 33 |  |  | 42 | $uri = URI->new($uri) | 
| 82 |  |  |  |  |  |  | unless blessed $uri && $uri->isa('URI'); | 
| 83 | 4 |  |  |  |  | 23576 | $self->{AS_server} = $uri; | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | #-------------------------- | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | sub select(@) | 
| 90 | 0 |  |  | 0 | 1 | 0 | {   my $self = shift; | 
| 91 | 0 | 0 | 0 |  |  | 0 | my $args = @_ && ref $_[0] eq 'HASH' ? shift : {}; | 
| 92 | 0 |  |  |  |  | 0 | $self->_select($args, scalar $self->expandSelect(@_)); | 
| 93 |  |  |  |  |  |  | } | 
| 94 | 0 |  |  | 0 |  | 0 | sub _select($$) {panic "not extended"} | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | sub queryTerms(@) | 
| 98 | 0 |  |  | 0 | 1 | 0 | {   my $self  = shift; | 
| 99 | 0 |  |  |  |  | 0 | $self->_terms(scalar $self->expandTerms(@_)); | 
| 100 |  |  |  |  |  |  | } | 
| 101 | 0 |  |  | 0 |  | 0 | sub _terms(@) {panic "not implemented"} | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | #------------------------------------- | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | sub addDocument($%) | 
| 106 | 0 |  |  | 0 | 1 | 0 | {   my ($self, $docs, %args) = @_; | 
| 107 | 0 | 0 |  |  |  | 0 | $docs  = [ $docs ] if ref $docs ne 'ARRAY'; | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 0 |  |  |  |  | 0 | my $sv = $self->serverVersion; | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 0 |  |  |  |  | 0 | my (%attrs, %params); | 
| 112 |  |  |  |  |  |  | $params{commit} | 
| 113 | 0 | 0 |  |  |  | 0 | = _to_bool(exists $args{commit} ? $args{commit} : $self->autocommit); | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 0 | 0 |  |  |  | 0 | if(my $cw = $args{commitWithin}) | 
| 116 | 0 | 0 |  |  |  | 0 | {   if($sv lt '3.4') { $attrs{commit} = 'true' } | 
|  | 0 |  |  |  |  | 0 |  | 
| 117 | 0 |  |  |  |  | 0 | else { $attrs{commitWithin} = int($cw * 1000) } | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | $attrs{overwrite} = _to_bool delete $args{overwrite} | 
| 121 | 0 | 0 |  |  |  | 0 | if exists $args{overwrite}; | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 |  |  |  |  | 0 | foreach my $depr (qw/allowDups overwritePending overwriteCommitted/) | 
| 124 | 0 | 0 |  |  |  | 0 | {   if(exists $args{$depr}) | 
| 125 | 0 | 0 |  |  |  | 0 | {      if($sv ge '4.0') { $self->removed("add($depr)"); delete $args{$depr} } | 
|  | 0 | 0 |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 126 | 0 |  |  |  |  | 0 | elsif($sv ge '1.0') { $self->deprecated("add($depr)") } | 
| 127 | 0 |  |  |  |  | 0 | else { $attrs{$depr} = _to_bool delete $args{$depr} } | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 | 0 |  |  |  |  | 0 | $self->_add($docs, \%attrs, \%params); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | sub commit(%) | 
| 136 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 137 | 0 |  |  |  |  | 0 | my $sv = $self->serverVersion; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 0 |  |  |  |  | 0 | my %attrs; | 
| 140 | 0 | 0 |  |  |  | 0 | if(exists $args{waitFlush}) | 
| 141 | 0 | 0 |  |  |  | 0 | {      if($sv ge '4.0') | 
|  |  | 0 |  |  |  |  |  | 
| 142 | 0 |  |  |  |  | 0 | { $self->removed("commit(waitFlush)"); delete $args{waitFlush} } | 
|  | 0 |  |  |  |  | 0 |  | 
| 143 | 0 |  |  |  |  | 0 | elsif($sv ge '1.4') { $self->deprecated("commit(waitFlush)") } | 
| 144 | 0 |  |  |  |  | 0 | else { $attrs{waitFlush} = _to_bool delete $args{waitFlush} } | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | $attrs{waitSearcher} = _to_bool delete $args{waitSearcher} | 
| 148 | 0 | 0 |  |  |  | 0 | if exists $args{waitSearcher}; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 0 | 0 |  |  |  | 0 | if(exists $args{softCommit}) | 
| 151 | 0 | 0 |  |  |  | 0 | {   if($sv lt '4.0') { $self->ignored("commit(softCommit)") } | 
|  | 0 |  |  |  |  | 0 |  | 
| 152 | 0 |  |  |  |  | 0 | else { $attrs{softCommit} = _to_bool delete $args{softCommit} } | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 0 | 0 |  |  |  | 0 | if(exists $args{expungeDeletes}) | 
| 156 | 0 | 0 |  |  |  | 0 | {   if($sv lt '1.4') { $self->ignored("commit(expungeDeletes)") } | 
|  | 0 |  |  |  |  | 0 |  | 
| 157 | 0 |  |  |  |  | 0 | else { $attrs{expungeDeletes} = _to_bool delete $args{expungeDeletes} } | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 0 |  |  |  |  | 0 | $self->_commit(\%attrs); | 
| 161 |  |  |  |  |  |  | } | 
| 162 | 0 |  |  | 0 |  | 0 | sub _commit($) {panic "not implemented"} | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | sub optimize(%) | 
| 166 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 167 | 0 |  |  |  |  | 0 | my $sv = $self->serverVersion; | 
| 168 |  |  |  |  |  |  |  | 
| 169 | 0 |  |  |  |  | 0 | my %attrs; | 
| 170 | 0 | 0 |  |  |  | 0 | if(exists $args{waitFlush}) | 
| 171 | 0 | 0 |  |  |  | 0 | {      if($sv ge '4.0') | 
|  |  | 0 |  |  |  |  |  | 
| 172 | 0 |  |  |  |  | 0 | { $self->removed("commit(waitFlush)"); delete $args{waitFlush} } | 
|  | 0 |  |  |  |  | 0 |  | 
| 173 | 0 |  |  |  |  | 0 | elsif($sv ge '1.4') { $self->deprecated("optimize(waitFlush)") } | 
| 174 | 0 |  |  |  |  | 0 | else { $attrs{waitFlush} = _to_bool delete $args{waitFlush} } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | $attrs{waitSearcher} = _to_bool delete $args{waitSearcher} | 
| 178 | 0 | 0 |  |  |  | 0 | if exists $args{waitSearcher}; | 
| 179 |  |  |  |  |  |  |  | 
| 180 | 0 | 0 |  |  |  | 0 | if(exists $args{softCommit}) | 
| 181 | 0 | 0 |  |  |  | 0 | {   if($sv lt '4.0') { $self->ignored("optimize(softCommit)") } | 
|  | 0 |  |  |  |  | 0 |  | 
| 182 | 0 |  |  |  |  | 0 | else { $attrs{softCommit} = _to_bool delete $args{softCommit} } | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 | 0 |  |  |  | 0 | if(exists $args{maxSegments}) | 
| 186 | 0 | 0 |  |  |  | 0 | {   if($sv lt '1.3') { $self->ignored("optimize(maxSegments)") } | 
|  | 0 |  |  |  |  | 0 |  | 
| 187 | 0 |  |  |  |  | 0 | else { $attrs{maxSegments} = delete $args{maxSegments} } | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 0 |  |  |  |  | 0 | $self->_optimize(\%attrs); | 
| 191 |  |  |  |  |  |  | } | 
| 192 | 0 |  |  | 0 |  | 0 | sub _optimize($) {panic "not implemented"} | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | sub delete(%) | 
| 196 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 197 |  |  |  |  |  |  |  | 
| 198 | 0 |  |  |  |  | 0 | my %attrs; | 
| 199 |  |  |  |  |  |  | $attrs{commit} | 
| 200 | 0 | 0 |  |  |  | 0 | = _to_bool(exists $args{commit} ? $args{commit} : $self->autocommit); | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 0 | 0 |  |  |  | 0 | if(exists $args{fromPending}) | 
| 203 | 0 |  |  |  |  | 0 | {   $self->deprecated("delete(fromPending)"); | 
| 204 | 0 |  |  |  |  | 0 | $attrs{fromPending}   = _to_bool delete $args{fromPending}; | 
| 205 |  |  |  |  |  |  | } | 
| 206 | 0 | 0 |  |  |  | 0 | if(exists $args{fromCommitted}) | 
| 207 | 0 |  |  |  |  | 0 | {   $self->deprecated("delete(fromCommitted)"); | 
| 208 | 0 |  |  |  |  | 0 | $attrs{fromCommitted} = _to_bool delete $args{fromCommitted}; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 0 |  |  |  |  | 0 | my @which; | 
| 212 | 0 | 0 |  |  |  | 0 | if(my $id = $args{id}) | 
| 213 | 0 | 0 |  |  |  | 0 | {    push @which, map +(id => $_), ref $id eq 'ARRAY' ? @$id : $id; | 
| 214 |  |  |  |  |  |  | } | 
| 215 | 0 | 0 |  |  |  | 0 | if(my $q  = $args{query}) | 
| 216 | 0 | 0 |  |  |  | 0 | {    push @which, map +(query => $_), ref $q  eq 'ARRAY' ? @$q  : $q; | 
| 217 |  |  |  |  |  |  | } | 
| 218 | 0 | 0 |  |  |  | 0 | @which or return; | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | # JSON calls do not accept multiple ids at once (it seems in 4.0) | 
| 221 | 0 |  |  |  |  | 0 | my $result; | 
| 222 | 0 | 0 | 0 |  |  | 0 | if($self->serverVersion ge '1.4' && !$self->isa('Apache::Solr::JSON')) | 
| 223 | 0 |  |  |  |  | 0 | {   $result = $self->_delete(\%attrs, \@which); | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | else | 
| 226 |  |  |  |  |  |  | {   # old servers accept only one id or query per delete | 
| 227 | 0 |  |  |  |  | 0 | $result = $self->_delete(\%attrs, [splice @which, 0, 2]) while @which; | 
| 228 |  |  |  |  |  |  | } | 
| 229 | 0 |  |  |  |  | 0 | $result; | 
| 230 |  |  |  |  |  |  | } | 
| 231 | 0 |  |  | 0 |  | 0 | sub _delete(@) {panic "not implemented"} | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | sub rollback() | 
| 235 | 0 |  |  | 0 | 1 | 0 | {   my $self = shift; | 
| 236 | 0 | 0 |  |  |  | 0 | $self->serverVersion ge '1.4' | 
| 237 |  |  |  |  |  |  | or error __x"rollback not supported by solr server"; | 
| 238 |  |  |  |  |  |  |  | 
| 239 | 0 |  |  |  |  | 0 | $self->_rollback; | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | sub extractDocument(@) | 
| 244 | 0 |  |  | 0 | 1 | 0 | {   my $self  = shift; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 0 | 0 |  |  |  | 0 | $self->serverVersion ge '1.4' | 
| 247 |  |  |  |  |  |  | or error __x"extractDocument() requires Solr v1.4 or higher"; | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 0 |  |  |  |  | 0 | my %p     = $self->expandExtract(@_); | 
| 250 | 0 |  |  |  |  | 0 | my $data; | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | # expand* changes '_' into '.' | 
| 253 | 0 |  |  |  |  | 0 | my $ct    = delete $p{'content.type'}; | 
| 254 | 0 |  |  |  |  | 0 | my $fn    = delete $p{file}; | 
| 255 | 0 | 0 | 0 |  |  | 0 | $p{'resource.name'} ||= $fn if $fn && !ref $fn; | 
|  |  |  | 0 |  |  |  |  | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | $p{commit}  = _to_bool $self->autocommit | 
| 258 | 0 | 0 |  |  |  | 0 | unless exists $p{commit}; | 
| 259 |  |  |  |  |  |  |  | 
| 260 | 0 | 0 |  |  |  | 0 | if(defined $p{string}) | 
|  |  | 0 |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | {   # try to avoid copying the data, which can be huge | 
| 262 |  |  |  |  |  |  | $data = $ct =~ m!^text/!i | 
| 263 |  |  |  |  |  |  | ? \encode(utf8 => | 
| 264 | 0 |  |  |  |  | 0 | (ref $p{string} eq 'SCALAR' ? ${$p{string}} : $p{string})) | 
| 265 | 0 | 0 |  |  |  | 0 | : (ref $p{string} eq 'SCALAR' ? $p{string} : \$p{string} ); | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 0 |  |  |  |  | 0 | delete $p{string}; | 
| 268 |  |  |  |  |  |  | } | 
| 269 |  |  |  |  |  |  | elsif($fn) | 
| 270 | 0 |  |  |  |  | 0 | {   local $/; | 
| 271 | 0 | 0 |  |  |  | 0 | if(ref $fn eq 'GLOB') { $data = \<$fn> } | 
|  | 0 |  |  |  |  | 0 |  | 
| 272 |  |  |  |  |  |  | else | 
| 273 | 0 |  |  |  |  | 0 | {   local *IN; | 
| 274 | 0 | 0 |  |  |  | 0 | open IN, '<:raw', $fn | 
| 275 |  |  |  |  |  |  | or fault __x"cannot read document from {fn}", fn => $fn; | 
| 276 | 0 |  |  |  |  | 0 | $data = \; | 
| 277 | 0 | 0 |  |  |  | 0 | close IN | 
| 278 |  |  |  |  |  |  | or fault __x"read error for document {fn}", fn => $fn; | 
| 279 | 0 |  | 0 |  |  | 0 | $ct ||= $mimetypes->mimeTypeOf($fn); | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | else | 
| 283 | 0 |  |  |  |  | 0 | {   error __x"extract requires document as file or string"; | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  |  | 
| 286 | 0 |  |  |  |  | 0 | $self->_extract([%p], $data, $ct); | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 0 |  |  | 0 |  | 0 | sub _extract($){panic "not implemented"} | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | #------------------------- | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | sub _core_admin($@) | 
| 293 | 0 |  |  | 0 |  | 0 | {   my ($self, $action, $params) = @_; | 
| 294 | 0 |  | 0 |  |  | 0 | $params->{core} ||= $self->core; | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 0 |  |  |  |  | 0 | my $endpoint = $self->endpoint('cores', core => 'admin' | 
| 297 |  |  |  |  |  |  | , params => $params); | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 0 |  |  |  |  | 0 | my @params   = %$params; | 
| 300 | 0 |  |  |  |  | 0 | my $result   = Apache::Solr::Result->new(params => [ %$params ] | 
| 301 |  |  |  |  |  |  | , endpoint => $endpoint, core => $self); | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 0 |  |  |  |  | 0 | $self->request($endpoint, $result); | 
| 304 | 0 |  |  |  |  | 0 | $result; | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | sub coreStatus(%) | 
| 309 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 310 | 0 |  |  |  |  | 0 | $self->_core_admin('STATUS', \%args); | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | sub coreReload(%) | 
| 315 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 316 | 0 |  |  |  |  | 0 | $self->_core_admin('RELOAD', \%args); | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub coreUnload($%) | 
| 321 | 0 |  |  | 0 | 1 | 0 | {   my ($self, %args) = @_; | 
| 322 | 0 |  |  |  |  | 0 | $self->_core_admin('UNLOAD', \%args); | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | #-------------------------- | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | sub _calling_sub() | 
| 328 | 1 |  |  | 1 |  | 4 | {   for(my $i=0;$i <10; $i++) | 
| 329 | 4 |  |  |  |  | 15 | {   my $sub = (caller $i)[3]; | 
| 330 | 4 | 100 | 66 |  |  | 16 | return $sub if !$sub || index($sub, 'Apache::Solr::') < 0; | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | sub _simpleExpand($$$) | 
| 335 | 26 |  |  | 26 |  | 35 | {   my ($self, $p, $prefix) = @_; | 
| 336 | 26 | 100 |  |  |  | 90 | my @p  = ref $p eq 'HASH' ? %$p : @$p; | 
| 337 | 26 |  |  |  |  | 38 | my $sv = $self->serverVersion; | 
| 338 |  |  |  |  |  |  |  | 
| 339 | 26 |  |  |  |  | 27 | my @t; | 
| 340 | 26 |  |  |  |  | 35 | while(@p) | 
| 341 | 90 |  |  |  |  | 125 | {   my ($k, $v) = (shift @p, shift @p); | 
| 342 | 90 |  |  |  |  | 116 | $k =~ s/_/./g; | 
| 343 | 90 | 100 | 100 |  |  | 203 | $k = $prefix.$k if defined $prefix && index($k, $prefix)!=0; | 
| 344 | 90 | 100 |  |  |  | 139 | my $param   = $k =~ m/^f\.[^\.]+\.(.*)/ ? $1 : $k; | 
| 345 |  |  |  |  |  |  |  | 
| 346 | 90 |  |  |  |  | 76 | my ($dv, $iv); | 
| 347 | 90 | 100 | 66 |  |  | 251 | if(($dv = $deprecated{$param}) && $sv ge $dv) | 
|  |  | 50 | 66 |  |  |  |  | 
| 348 | 1 |  |  |  |  | 3 | {   my $command = _calling_sub; | 
| 349 | 1 |  |  |  |  | 8 | $self->deprecated("$command($param) since $dv"); | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  | elsif(($iv = $introduced{$param}) && $iv gt $sv) | 
| 352 | 0 |  |  |  |  | 0 | {   my $command = _calling_sub; | 
| 353 | 0 |  |  |  |  | 0 | $self->ignored("$command($param) introduced in $iv"); | 
| 354 | 0 |  |  |  |  | 0 | next; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | push @t, $k => $boolparams{$param} ? _to_bool($_) : $_ | 
| 358 | 90 | 100 |  |  |  | 509 | for ref $v eq 'ARRAY' ? @$v : $v; | 
|  |  | 100 |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | } | 
| 360 | 26 |  |  |  |  | 121 | @t; | 
| 361 |  |  |  |  |  |  | } | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | sub expandTerms(@) | 
| 365 | 1 |  |  | 1 | 1 | 821 | {   my $self = shift; | 
| 366 | 1 | 50 |  |  |  | 8 | my $p    = @_==1 ? shift : [@_]; | 
| 367 | 1 |  |  |  |  | 6 | my @t    = $self->_simpleExpand($p, 'terms.'); | 
| 368 | 1 | 50 |  |  |  | 8 | wantarray ? @t : \@t; | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | sub _expand_flatten($$) | 
| 373 | 4 |  |  | 4 |  | 6 | {   my ($self, $v, $prefix) = @_; | 
| 374 | 4 | 50 |  |  |  | 12 | my @l = ref $v eq 'HASH' ? %$v : @$v; | 
| 375 | 4 |  |  |  |  | 4 | my @s; | 
| 376 | 4 |  |  |  |  | 11 | push @s, $prefix.(shift @l) => (shift @l) while @l; | 
| 377 | 4 |  |  |  |  | 12 | @s; | 
| 378 |  |  |  |  |  |  | } | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | sub expandExtract(@) | 
| 381 | 1 |  |  | 1 | 1 | 622 | {   my $self = shift; | 
| 382 | 1 | 50 |  |  |  | 6 | my @p = @_==1 ? @{(shift)} : @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 383 | 1 |  |  |  |  | 2 | my @s; | 
| 384 | 1 |  |  |  |  | 3 | while(@p) | 
| 385 | 10 |  |  |  |  | 13 | {   my ($k, $v) = (shift @p, shift @p); | 
| 386 | 10 | 100 | 66 |  |  | 58 | if(!ref $v || ref $v eq 'SCALAR') | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 50 | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 387 | 6 |  |  |  |  | 12 | { push @s, $k => $v } | 
| 388 |  |  |  |  |  |  | elsif($k eq 'literal' || $k eq 'literals') | 
| 389 | 2 |  |  |  |  | 7 | { push @s, $self->_expand_flatten($v, 'literal.') } | 
| 390 |  |  |  |  |  |  | elsif($k eq 'fmap' || $k eq 'boost' || $k eq 'resource') | 
| 391 | 2 |  |  |  |  | 6 | { push @s, $self->_expand_flatten($v, "$k.") } | 
| 392 | 0 |  |  |  |  | 0 | else { panic "unknown set '$k'" } | 
| 393 |  |  |  |  |  |  | } | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 1 | 50 |  |  |  | 5 | my @t = @s ? $self->_simpleExpand(\@s) : (); | 
| 396 | 1 | 50 |  |  |  | 15 | wantarray ? @t : \@t; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | # probably more config later, currently only one column | 
| 401 |  |  |  |  |  |  | # "also-per-field" means, not only $set.$more, but also f.$field.$set.$more | 
| 402 |  |  |  |  |  |  | my %sets =   #also-per-field? | 
| 403 |  |  |  |  |  |  | ( expand  => [0] | 
| 404 |  |  |  |  |  |  | , facet   => [1] | 
| 405 |  |  |  |  |  |  | , hl      => [1] | 
| 406 |  |  |  |  |  |  | , mlt     => [0] | 
| 407 |  |  |  |  |  |  | , stats   => [0] | 
| 408 |  |  |  |  |  |  | , suggest => [0] | 
| 409 |  |  |  |  |  |  | , group   => [0] | 
| 410 |  |  |  |  |  |  | ); | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | sub expandSelect(@) | 
| 413 | 9 |  |  | 9 | 1 | 1662 | {   my $self = shift; | 
| 414 | 9 |  |  |  |  | 14 | my @s; | 
| 415 | 9 |  |  |  |  | 11 | my (@flat, %seen_set); | 
| 416 | 9 |  |  |  |  | 39 | while(@_) | 
| 417 | 37 |  |  |  |  | 52 | {   my ($k, $v) = (shift, shift); | 
| 418 | 37 |  |  |  |  | 50 | $k =~ s/_/./g; | 
| 419 | 37 |  |  |  |  | 57 | my @p = split /\./, $k; | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # fields are $set.$more or f.$field.$set.$more | 
| 422 | 37 |  | 66 |  |  | 67 | my $per_field    = $p[0] eq 'f' && @p > 2; | 
| 423 | 37 | 100 |  |  |  | 66 | my ($set, $more) = $per_field ? @p[2,3] : @p[0,1]; | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 37 | 100 |  |  |  | 72 | if(my $def = $sets{$set}) | 
|  |  | 50 |  |  |  |  |  | 
| 426 | 15 |  |  |  |  | 21 | {   $seen_set{$set} = 1; | 
| 427 | 15 | 50 | 66 |  |  | 26 | !$per_field || $def->[0] | 
| 428 |  |  |  |  |  |  | or error __x"set {set} cannot be used per field, in {field}" | 
| 429 |  |  |  |  |  |  | , set => $set, field => $k; | 
| 430 |  |  |  |  |  |  |  | 
| 431 | 15 | 50 |  |  |  | 26 | if(ref $v eq 'HASH') | 
|  |  | 0 |  |  |  |  |  | 
| 432 | 15 | 50 |  |  |  | 19 | {   !$more | 
| 433 |  |  |  |  |  |  | or error __x"field {field} is not simple for a set", field => $k; | 
| 434 | 15 |  |  |  |  | 35 | push @s, $self->_simpleExpand($v, "$k."); | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  | elsif($more)    # skip $set=true for now | 
| 437 | 0 |  |  |  |  | 0 | {   push @flat, $k => $v; | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  | elsif(ref $v eq 'HASH') | 
| 441 | 0 |  |  |  |  | 0 | {   error __x"unknown set {set}", set => $set; | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  | else | 
| 444 | 22 |  |  |  |  | 46 | {   push @flat, $k => $v; | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  | } | 
| 447 | 9 |  |  |  |  | 17 | push @flat, %seen_set; | 
| 448 | 9 |  |  |  |  | 18 | unshift @s, $self->_simpleExpand(\@flat); | 
| 449 | 9 | 100 |  |  |  | 41 | wantarray ? @s : \@s; | 
| 450 |  |  |  |  |  |  | } | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | sub deprecated($) | 
| 454 | 1 |  |  | 1 | 1 | 2 | {   my ($self, $msg) = @_; | 
| 455 | 1 | 50 |  |  |  | 4 | return if $self->{AS_depr_msg}{$msg}++;  # report only once | 
| 456 | 1 |  |  |  |  | 4 | warning __x"deprecated solr {message}", message => $msg; | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | sub ignored($) | 
| 461 | 0 |  |  | 0 | 1 | 0 | {   my ($self, $msg) = @_; | 
| 462 | 0 | 0 |  |  |  | 0 | return if $self->{AS_ign_msg}{$msg}++;  # report only once | 
| 463 | 0 |  |  |  |  | 0 | warning __x"ignored solr {message}", message => $msg; | 
| 464 |  |  |  |  |  |  | } | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | sub removed($) | 
| 468 | 0 |  |  | 0 | 1 | 0 | {   my ($self, $msg) = @_; | 
| 469 | 0 | 0 |  |  |  | 0 | return if $self->{AS_rem_msg}{$msg}++;  # report only once | 
| 470 | 0 |  |  |  |  | 0 | warning __x"removed solr {message}", message => $msg; | 
| 471 |  |  |  |  |  |  | } | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | #------------------------ | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | sub endpoint($@) | 
| 477 | 1 |  |  | 1 | 1 | 772 | {   my ($self, $action, %args) = @_; | 
| 478 | 1 |  | 33 |  |  | 7 | my $core = $args{core} || $self->core; | 
| 479 | 1 |  |  |  |  | 3 | my $take = $self->server->clone; | 
| 480 | 1 | 50 |  |  |  | 55 | $take->path ($take->path . (defined $core ? "/$core" : '') . "/$action"); | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | # make parameters ordered | 
| 483 | 1 |  | 50 |  |  | 60 | my $params = $args{params} || []; | 
| 484 | 1 | 50 |  |  |  | 4 | $params    = [ %$params ] if ref $params eq 'HASH'; | 
| 485 | 1 | 50 |  |  |  | 3 | @$params or return $take; | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | # remove paramers with undefined value | 
| 488 | 1 |  |  |  |  | 2 | my @p = @$params; | 
| 489 | 1 |  |  |  |  | 2 | my @params; | 
| 490 | 1 |  |  |  |  | 2 | while(@p) | 
| 491 | 2 | 50 |  |  |  | 4 | {   push @params, $p[0] => $p[1] if defined $p[1]; | 
| 492 | 2 |  |  |  |  | 5 | shift @p, shift @p; | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  |  | 
| 495 | 1 | 50 |  |  |  | 7 | $take->query_form(@params) if @params; | 
| 496 | 1 |  |  |  |  | 100 | $take; | 
| 497 |  |  |  |  |  |  | } | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | sub request($$;$$) | 
| 500 | 0 |  |  | 0 | 0 |  | {   my ($self, $url, $result, $body, $body_ct) = @_; | 
| 501 |  |  |  |  |  |  |  | 
| 502 | 0 |  |  |  |  |  | my $req; | 
| 503 | 0 | 0 |  |  |  |  | if(!$body) | 
| 504 |  |  |  |  |  |  | {   # request without payload | 
| 505 | 0 |  |  |  |  |  | $req = HTTP::Request->new(GET => $url); | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  | else | 
| 508 |  |  |  |  |  |  | {   # request with 'form' payload | 
| 509 | 0 | 0 |  |  |  |  | $req       = HTTP::Request->new | 
| 510 |  |  |  |  |  |  | ( POST => $url | 
| 511 |  |  |  |  |  |  | , [ Content_Type        => $body_ct | 
| 512 |  |  |  |  |  |  | , Content_Disposition => 'form-data; name="content"' | 
| 513 |  |  |  |  |  |  | ] | 
| 514 |  |  |  |  |  |  | , (ref $body eq 'SCALAR' ? $$body : $body) | 
| 515 |  |  |  |  |  |  | ); | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  |  | 
| 518 | 0 |  |  |  |  |  | $result->request($req); | 
| 519 |  |  |  |  |  |  |  | 
| 520 | 0 |  |  |  |  |  | my $resp = $self->agent->request($req); | 
| 521 |  |  |  |  |  |  | #use Data::Dumper; | 
| 522 |  |  |  |  |  |  | #warn Dumper $resp; | 
| 523 | 0 |  |  |  |  |  | $result->response($resp); | 
| 524 | 0 |  |  |  |  |  | $resp; | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  |  | 
| 527 |  |  |  |  |  |  | #---------------------------------- | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | 1; |