File Coverage

blib/lib/Couch/DB/Result.pm
Criterion Covered Total %
statement 27 179 15.0
branch 0 58 0.0
condition 0 41 0.0
subroutine 9 50 18.0
pod 36 37 97.3
total 72 365 19.7


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Couch-DB version 0.201.
2             # The POD got stripped from this file by OODoc version 3.06.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2024-2026 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Couch::DB::Result;{
13             our $VERSION = '0.201';
14             }
15              
16              
17 1     1   1438 use warnings;
  1         4  
  1         65  
18 1     1   7 use strict;
  1         2  
  1         36  
19              
20 1     1   5 use Couch::DB::Util qw/flat pile/;
  1         2  
  1         8  
21 1     1   7 use Couch::DB::Document ();
  1         2  
  1         19  
22 1     1   41 use Couch::DB::Row ();
  1         3  
  1         39  
23              
24 1     1   6 use Log::Report 'couch-db';
  1         3  
  1         36  
25 1     1   336 use HTTP::Status qw/is_success status_constant_name HTTP_OK HTTP_CONTINUE HTTP_MULTIPLE_CHOICES/;
  1         2  
  1         337  
26 1     1   9 use Scalar::Util qw/weaken blessed/;
  1         2  
  1         173  
27              
28             my %couch_code_names = (); # I think I saw them somewhere. Maybe none
29              
30             my %default_code_texts = ( # do not construct them all the time again
31             &HTTP_OK => 'Data collected successfully.',
32             &HTTP_CONTINUE => 'The data collection is delayed.',
33             &HTTP_MULTIPLE_CHOICES => 'The Result object does not know what to do, yet.',
34             );
35              
36             my $seqnr = 0;
37              
38             #--------------------
39              
40             use overload
41 0     0     bool => sub { $_[0]->code < 400 },
42 1         14 '""' => 'short',
43 1     1   8 fallback => 1;
  1         1  
44              
45             #--------------------
46              
47 0     0 1   sub new(@) { my ($class, %args) = @_; (bless {}, $class)->init(\%args) }
  0            
48              
49             sub init($)
50 0     0 0   { my ($self, $args) = @_;
51              
52 0 0         $self->{CDR_couch} = delete $args->{couch} or panic;
53 0           $self->{CDR_on_final} = pile delete $args->{on_final};
54 0           $self->{CDR_on_error} = pile delete $args->{on_error};
55 0           $self->{CDR_on_chain} = pile delete $args->{on_chain};
56 0           $self->{CDR_on_values} = pile delete $args->{on_values};
57 0           $self->{CDR_on_row} = pile delete $args->{on_row};
58 0           $self->{CDR_code} = HTTP_MULTIPLE_CHOICES;
59 0           $self->{CDR_page} = delete $args->{paging};
60 0           $self->{CDR_seqnr} = ++$seqnr;
61              
62 0           $self;
63             }
64              
65             #--------------------
66              
67 0     0 1   sub couch() { $_[0]->{CDR_couch} }
68 0     0 1   sub isDelayed() { $_[0]->code == HTTP_CONTINUE }
69 0     0 1   sub isReady() { $_[0]->{CDR_ready} }
70              
71              
72 0     0 1   sub code() { $_[0]->{CDR_code} }
73              
74              
75             sub codeName(;$)
76 0     0 1   { my ($self, $code) = @_;
77 0   0       $code ||= $self->code;
78 0 0 0       status_constant_name($code) || couch_code_names{$code} || $code;
  0            
79             }
80              
81              
82             sub message()
83 0     0 1   { my $self = shift;
84 0 0 0       $self->{CDR_msg} || $default_code_texts{$self->code} || $self->codeName;
85             }
86              
87              
88             sub setStatus($$)
89 0     0 1   { my ($self, $code, $msg) = @_;
90 0           $self->{CDR_code} = $code;
91 0           $self->{CDR_msg} = $msg;
92 0           $self;
93             }
94              
95              
96 0     0 1   sub seqnr() { $_[0]->{CDR_seqnr} }
97              
98              
99             sub short()
100 0     0 1   { my $self = shift;
101 0           my $client = $self->client;
102 0           my $req = $self->request;
103              
104 0 0 0       $client && $req
105             ? (sprintf "RESULT %07d.%08d %-6s %s\n", $client->seqnr, $self->seqnr, $req->method, $req->url =~ s/\?.*/?.../r)
106             : (sprintf "RESULT prepare.%08d\n", $self->seqnr);
107             }
108              
109             #--------------------
110              
111 0     0 1   sub client() { $_[0]->{CDR_client} }
112 0     0 1   sub request() { $_[0]->{CDR_request} }
113 0     0 1   sub response() { $_[0]->{CDR_response} }
114              
115              
116             sub answer(%)
117 0     0 1   { my ($self, %args) = @_;
118              
119             return $self->{CDR_answer}
120 0 0         if defined $self->{CDR_answer};
121              
122 0 0         $self->isReady
123             or error __x"document not ready: {err}", err => $self->message;
124              
125 0           $self->{CDR_answer} = $self->couch->_extractAnswer($self->response);
126             }
127              
128              
129             sub values(@)
130 0     0 1   { my $self = shift;
131 0 0         return $self->{CDR_values} if exists $self->{CDR_values};
132              
133 0           my $values = $self->answer;
134 0           $values = $_->($self, $values) for reverse @{$self->{CDR_on_values}};
  0            
135 0           $self->{CDR_values} = $values;
136             }
137              
138             #--------------------
139              
140 0     0 1   sub rows(;$) { @{$_[0]->rowsRef($_[1])} }
  0            
141              
142              
143             sub rowsRef(;$)
144 0     0 1   { my ($self, $qnr) = @_;
145              
146 0 0         ! $self->inPagingMode
147             or panic "Call used in paging mode, so use the page* methods.";
148              
149 0   0       $self->_rowsRef($qnr // 0);
150             }
151              
152             sub _rowsRef($)
153 0     0     { my ($self, $qnr) = @_;
154 0   0       my $rows = $self->{CDR_rows}[$qnr] ||= [];
155 0 0         return $rows if $self->{CDR_rows_complete}[$qnr];
156              
157 0           for(my $rownr = 1; $self->row($rownr, $qnr); $rownr++) { }
158 0           $self->{CDR_rows_complete}[$qnr] = 1;
159 0           $rows;
160             }
161              
162              
163             sub row($;$)
164 0     0 1   { my ($self, $rownr, $qnr) = @_;
165 0   0       my $rows = $self->{CDR_rows}[$qnr //= 0] ||= [];
      0        
166 0           my $index = $rownr -1;
167 0 0         return $rows->[$index] if exists $rows->[$index];
168              
169 0           my %data = map $_->($self, $rownr-1, column => $qnr), reverse @{$self->{CDR_on_row}};
  0            
170 0 0         keys %data or return ();
171              
172 0           my $doc;
173 0   0       my $dp = delete $data{docparams} || {};
174 0 0         if(my $dd = delete $data{docdata})
    0          
175 0           { $doc = Couch::DB::Document->fromResult($self, $dd, %$dp);
176             }
177             elsif($dd = delete $data{ddocdata})
178 0           { $doc = Couch::DB::Design->fromResult($self, $dd, %$dp);
179             }
180              
181 0           my $row = Couch::DB::Row->new(%data, result => $self, rownr => $rownr, doc => $doc);
182 0 0         $doc->row($row) if $doc;
183              
184 0           $rows->[$index] = $row; # Remember partial result for rows()
185             }
186              
187              
188 0     0 1   sub numberOfRows(;$) { scalar @{$_[0]->rowsRef($_[1])} }
  0            
189              
190              
191 0     0 1   sub docs(;$) { map $_->doc, $_[0]->rows($_[1]) }
192              
193              
194 0     0 1   sub docsRef(;$) { [ map $_->doc, $_[0]->rows($_[1]) ] }
195              
196              
197             sub doc($;$)
198 0     0 1   { my ($self, $rownr, $qnr) = @_;
199 0           my $r = $self->row($rownr, $qnr);
200 0 0         defined $r ? $r->doc : undef;
201             }
202              
203             #--------------------
204              
205             sub pagingState(%)
206 0     0 1   { my ($self, %args) = @_;
207 0           my $next = $self->nextPageSettings;
208 0 0         $next->{harvester} = defined $next->{harvester} ? 'CODE' : 'DEFAULT';
209 0 0         $next->{map} = defined $next->{map} ? 'CODE' : 'NONE';
210 0           $next->{client} = $self->client->name;
211              
212 0 0 0       if(my $maxbook = delete $args{max_bookmarks} // 10)
213 0           { my $bookmarks = $next->{bookmarks};
214 0 0         $next->{bookmarks} = +{ (%$bookmarks)[0..(2*$maxbook-1)] } if keys %$bookmarks > $maxbook;
215             }
216              
217 0           $next;
218             }
219              
220              
221 0     0 1   sub supportsPaging() { defined $_[0]->{CDR_page} }
222              
223              
224 0 0   0 1   sub inPagingMode() { my $r = $_[0]->{CDR_page}; $r && $r->{page_mode} }
  0            
225              
226             # The next is used r/w when succeed is a result object, and when results
227             # have arrived.
228              
229 0 0   0     sub _thisPage() { $_[0]->{CDR_page} or panic "Call does not support paging." }
230              
231              
232             sub nextPageSettings()
233 0     0 1   { my $self = shift;
234 0           my %next = %{$self->_thisPage};
  0            
235 0           delete $next{harvested};
236 0           $next{start} += (delete $next{skip}) + @{$self->_rowsRef(0)};
  0            
237 0           $next{pagenr}++;
238 0           \%next;
239             }
240              
241              
242             sub page()
243 0     0 1   { my $self = shift;
244              
245 0 0         $self->inPagingMode
246             or panic "Call not in paging mode, use the row* and doc* alternative methods.";
247              
248 0           $self->_thisPage->{harvested};
249             }
250              
251             sub _pageAdd($$)
252 0     0     { my ($self, $bookmark, $found) = @_;
253 0           my $this = $self->_thisPage;
254 0           my $page = $this->{harvested};
255 0           push @$page, @$found;
256              
257 0 0         if(defined $bookmark)
258 0           { my $recv = $this->{start} + $this->{skip} + @$page;
259 0           $this->{bookmarks}{$recv} = $bookmark;
260             }
261              
262 0   0       $this->{end_reached} = ! @$found || $this->{stop}->($self);
263 0           $page;
264             }
265              
266              
267 0     0 1   sub pageRows() { @{$_[0]->page} }
  0            
268              
269              
270 0     0 1   sub pageNumber() { $_[0]->_thisPage->{pagenr} }
271              
272              
273 0     0 1   sub pageDocs() { map $_->doc, @{$_[0]->page} }
  0            
274              
275              
276 0 0   0 1   sub pageDoc($) { my $r = $_[0]->page->[$_[1]-1]; defined $r ? $r->doc : undef }
  0            
277              
278              
279             sub pageIsPartial()
280 0     0 1   { my $this = shift->_thisPage;
281             $this->{page_mode}
282             && ! $this->{end_reached}
283 0 0 0       && ($this->{all} || @{$this->{harvested}} < $this->{page_size});
  0   0        
284             }
285              
286              
287 0     0 1   sub isLastPage() { $_[0]->_thisPage->{end_reached} }
288              
289             #--------------------
290              
291             sub setFinalResult($%)
292 0     0 1   { my ($self, $data, %args) = @_;
293 0   0       my $code = delete $data->{code} || HTTP_OK;
294              
295 0 0         $self->{CDR_client} = my $client = delete $data->{client} or panic "No client";
296 0           weaken $self->{CDR_client};
297              
298 0           $self->{CDR_ready} = 1;
299 0           $self->{CDR_request} = delete $data->{request};
300 0           $self->{CDR_response} = delete $data->{response};
301 0           $self->setStatus($code, delete $data->{message});
302              
303 0           delete $self->{CDR_answer}; # remove cached while paging
304 0           delete $self->{CDR_values};
305 0           delete $self->{CDR_rows};
306              
307             # "on_error" handler
308 0 0         unless(is_success $code)
309 0           { $_->($self) for @{$self->{CDR_on_error}};
  0            
310 0           return undef;
311             }
312              
313             # "on_final" handler
314 0           $_->($self) for @{$self->{CDR_on_final}};
  0            
315              
316             # "on_change" handler
317             # First run inner chains, working towards outer
318 0 0         my @chains = @{$self->{CDR_on_chain} || []};
  0            
319 0           my $tail = $self;
320              
321 0   0       while(@chains && $tail)
322 0           { $tail = (pop @chains)->($tail);
323 0 0 0       blessed $tail && $tail->isa('Couch::DB::Result')
324             or panic "Chain must return a Result object";
325             }
326              
327 0           $tail;
328             }
329              
330              
331             sub setResultDelayed($%)
332 0     0 1   { my ($self, $plan, %args) = @_;
333              
334 0           $self->{CDR_delayed} = $plan;
335 0           $self->setStatus(HTTP_CONTINUE);
336 0           $self;
337             }
338              
339              
340 0     0 1   sub delayPlan() { $_[0]->{CDR_delayed} }
341              
342             #--------------------
343              
344             1;