File Coverage

blib/lib/Protocol/Dqlite.pm
Criterion Covered Total %
statement 175 197 88.8
branch 36 48 75.0
condition 6 21 28.5
subroutine 38 50 76.0
pod 3 3 100.0
total 258 319 80.8


line stmt bran cond sub pod time code
1             package Protocol::Dqlite;
2              
3 2     2   137802 use strict;
  2         11  
  2         48  
4 2     2   9 use warnings;
  2         3  
  2         73  
5              
6             our $VERSION = '0.01_90';
7              
8             # https://dqlite.io/docs/protocol
9             # https://github.com/canonical/dqlite/blob/master/src/request.h
10             # https://github.com/canonical/dqlite/blob/master/src/response.h
11              
12             =encoding utf-8
13              
14             =head1 NAME
15              
16             Protocol::Dqlite - L in Perl
17              
18             =head1 SYNOPSIS
19              
20             use autodie;
21              
22             my $dqlite = Protocol::Dqlite->new();
23              
24             my $socket = IO::Socket::INET->new('127.0.0.1:9001') or die;
25              
26             syswrite $s, Protocol::Dqlite::handshake();
27              
28             # Register ourselves as a Dqlite client:
29             syswrite $s, Protocol::Dqlite::request( Protocol::Dqlite::REQUEST_CLIENT, 0 );
30              
31             # Await the server’s acknowledgement:
32             while (sysread $s, my $buf, 512) {
33             my ($msg) = $dqlite->feed($buf);
34             next if !$msg;
35              
36             last if $msg isa Protocol::Dqlite::Response::WELCOME;
37              
38             # An unexpected response. Better bail out …
39             #
40             require Data::Dumper;
41             die Data::Dumper::Dumper($msg);
42             }
43              
44             … and now you can exchange messages as you wish.
45              
46             =head1 DESCRIPTION
47              
48             This module implements message parsing and creation for
49             L clients. To use this module you’ll need to
50             write the I/O logic yourself.
51              
52             =head1 CHARACTER ENCODING
53              
54             Strings designated as “blob”s are I strings.
55              
56             All other strings into & out of this module are I strings.
57              
58             Decode & encode accordingly.
59              
60             =cut
61              
62 2     2   10 use Carp ();
  2         2  
  2         120  
63              
64             my ( $PACK_STRING, $PACK_U64, $PACK_I64, $PACK_ALIGN_U64 );
65              
66             BEGIN {
67 2     2   6 $PACK_ALIGN_U64 = 'x![q]';
68 2         4 $PACK_STRING = "Z* $PACK_ALIGN_U64";
69 2         3 $PACK_U64 = 'Q<';
70 2         4 $PACK_I64 = 'q<';
71              
72 2 50       4 eval { pack $PACK_U64 } or Carp::croak "64-bit perl is required!";
  2         151  
73             }
74              
75             use constant {
76 2         529 _PROTOCOL_VERSION => 1,
77              
78             _HEADER_TEMPLATE => 'V C x3',
79              
80             _U64_LEN => length( pack $PACK_U64 ),
81              
82             TUPLE_INT64 => 1,
83             TUPLE_FLOAT => 2,
84             TUPLE_STRING => 3,
85             TUPLE_BLOB => 4,
86             TUPLE_NULL => 5,
87             TUPLE_UNIXTIME => 9,
88             TUPLE_ISO8601 => 10,
89             TUPLE_BOOLEAN => 11,
90              
91             REQUEST_LEADER => 0,
92             REQUEST_CLIENT => 1,
93              
94             #REQUEST_HEARTBEAT => 2,
95             REQUEST_OPEN => 3,
96             REQUEST_PREPARE => 4,
97             REQUEST_EXEC => 5,
98             REQUEST_QUERY => 6,
99             REQUEST_FINALIZE => 7,
100             REQUEST_EXEC_SQL => 8,
101             REQUEST_QUERY_SQL => 9,
102             REQUEST_INTERRUPT => 10,
103             REQUEST_CONNECT => 11,
104             REQUEST_ADD => 12,
105             REQUEST_ASSIGN => 13,
106             REQUEST_REMOVE => 14,
107             REQUEST_DUMP => 15,
108             REQUEST_CLUSTER => 16,
109              
110 2     2   11 };
  2         4  
111              
112             use constant {
113 2         802 _HEADER_SIZE => length pack(_HEADER_TEMPLATE),
114             handshake => pack( $PACK_U64, _PROTOCOL_VERSION ),
115 2     2   12 };
  2         4  
116              
117             my $_NODE_INFO_TEMPLATE = "$PACK_U64 $PACK_STRING $PACK_U64";
118              
119             my @REQUEST_PACK;
120             @REQUEST_PACK[
121             REQUEST_LEADER,
122             REQUEST_CLIENT,
123             REQUEST_OPEN,
124             REQUEST_PREPARE,
125              
126             REQUEST_EXEC,
127             REQUEST_QUERY,
128             REQUEST_FINALIZE,
129             REQUEST_EXEC_SQL,
130              
131             REQUEST_QUERY_SQL,
132             REQUEST_INTERRUPT,
133             REQUEST_CONNECT,
134             REQUEST_ADD,
135              
136             REQUEST_ASSIGN,
137             REQUEST_REMOVE,
138             REQUEST_DUMP,
139             REQUEST_CLUSTER,
140             ]
141             = (
142             $PACK_U64,
143             $PACK_U64,
144             "$PACK_STRING $PACK_U64 $PACK_STRING",
145             "$PACK_U64 $PACK_STRING",
146              
147             'V V', # plus tuple
148             'V V', # plus tuple
149             'V V', # no tuple
150             "$PACK_U64 $PACK_STRING",
151              
152             "$PACK_U64 $PACK_STRING",
153             $PACK_U64,
154             $_NODE_INFO_TEMPLATE,
155             $_NODE_INFO_TEMPLATE,
156              
157             $PACK_U64,
158             $PACK_U64,
159             $PACK_STRING,
160             $PACK_U64,
161             );
162              
163             my $PACK_DQLITE_BLOB = "$PACK_U64/a $PACK_ALIGN_U64";
164              
165             my @TUPLE_PACK;
166             @TUPLE_PACK[ #
167             TUPLE_INT64,
168             TUPLE_FLOAT,
169             TUPLE_STRING,
170             TUPLE_BLOB,
171             TUPLE_NULL,
172             TUPLE_UNIXTIME,
173             TUPLE_ISO8601,
174             TUPLE_BOOLEAN,
175             ]
176             = ( #
177             $PACK_I64,
178             'd<',
179             $PACK_STRING,
180             $PACK_DQLITE_BLOB,
181             "x[$PACK_U64]",
182             $PACK_I64,
183             $PACK_STRING,
184             $PACK_U64,
185             );
186              
187             my @TUPLE_PACK_LENGTH = map { $_ && length pack $_ } @TUPLE_PACK;
188             my %TUPLE_PACK_LENGTH_VARIADIC =
189             map { $_ => 1 } ( TUPLE_STRING, TUPLE_BLOB, TUPLE_ISO8601 );
190              
191             my $TUPLE_BLOB_BASE_LENGTH = length pack $TUPLE_PACK[TUPLE_BLOB], q<>;
192              
193             my $_NODE_INFO_COUNT =
194             @{ [ unpack $_NODE_INFO_TEMPLATE, pack $_NODE_INFO_TEMPLATE ] };
195              
196             my $_DQLITE_RESPONSE_ROWS_PART = "\xee" x _U64_LEN;
197             my $_DQLITE_RESPONSE_ROWS_DONE = "\xff" x _U64_LEN;
198              
199             my @RESPONSE_METADATA = (
200              
201             # $classname, $pack_template, @text_members
202              
203             [ 'FAILURE', "$PACK_U64 $PACK_STRING", 1 ],
204             [ 'SERVER', $_NODE_INFO_TEMPLATE, 1 ],
205             ['WELCOME'],
206              
207             # special snowflake: have to UTF-8-decode 2, 5, 8, ...
208             [ 'SERVERS', "$PACK_U64 ($_NODE_INFO_TEMPLATE)*" ],
209              
210             [ 'DB', "VV" ],
211             [ 'STMT', "VV $PACK_U64" ],
212             [ 'RESULT', "$PACK_U64 $PACK_U64" ],
213              
214             # special snowflake: have to decode tuples
215             ['ROWS'],
216             ['EMPTY'],
217             [ 'FILES', "$PACK_U64/($PACK_STRING $PACK_DQLITE_BLOB)", 0 ],
218             );
219              
220             my %REQUEST_HAS_SQL_AT_1 =
221             map { $_ => 1 } ( REQUEST_PREPARE, REQUEST_QUERY_SQL, REQUEST_EXEC_SQL, );
222              
223             my %REQUEST_HAS_TUPLE_AT_2 =
224             map { $_ => 1 } ( keys %REQUEST_HAS_SQL_AT_1, REQUEST_EXEC, );
225              
226             #----------------------------------------------------------------------
227              
228             =head1 CONSTANTS
229              
230             The following derive from the
231             L
232             as well as
233             L
234             in Dqlite’s source code.
235              
236             =head2 Role Codes
237              
238             C, C, C
239              
240             =cut
241              
242             use constant {
243 2         2705 ROLE_VOTER => 0,
244             ROLE_STANDBY => 1,
245             ROLE_SPARE => 2,
246 2     2   14 };
  2         3  
247              
248             =head2 Tuple Member Types
249              
250             =over
251              
252             =item * Numbers: C, C
253              
254             =item * Buffers: C, C
255              
256             =item * Time: C, C
257              
258             =item * Other: C, C
259              
260             =back
261              
262             =head2 Request Message Types
263              
264             =over
265              
266             =item * C (0)
267              
268             =item * C (1)
269              
270             =item * C (3)
271              
272             =item * C (4)
273              
274             =item * C (5)
275              
276             =item * C (6)
277              
278             =item * C (7)
279              
280             =item * C (8)
281              
282             =item * C (9)
283              
284             =item * C (10)
285              
286             =item * C (11)
287              
288             =item * C (12)
289              
290             =item * C (13)
291              
292             =item * C (14)
293              
294             =item * C (15)
295              
296             =item * C (16)
297              
298             =back
299              
300             =head2 Other
301              
302             =over
303              
304             =item * C - The string to send after establishing
305             a connection to the server.
306              
307             =back
308              
309             =cut
310              
311             #----------------------------------------------------------------------
312              
313             =head1 STATIC FUNCTIONS
314              
315             =head2 $bytes = request( $TYPE, @ARGUMENTS )
316              
317             Returns a byte buffer of a Dqlite message. $TYPE is one of the
318             C constants listed above.
319              
320             @ARGUMENTS are as Dqlite’s
321             L indicates.
322             Dqlite tuples are expressed as type/value pairs.
323              
324             Example:
325              
326             my $bytes = Protocol::Dqlite::request(
327             Protocol::Dqlite::REQUEST_PREPARE,
328             12,
329             "SELECT ?, ?",
330             Protocol::Dqlite::TUPLE_INT64 => 12345,
331             Protocol::Dqlite::TUPLE_BLOB => "\x00\x01\x02",
332             );
333              
334             =cut
335              
336             sub request {
337 6     6 1 2499 my ( $type, @vars ) = @_;
338              
339 6   33     16 my $tmpl =
340             $REQUEST_PACK[$type] || Carp::croak("Unknown request type: $type");
341              
342 6 100       16 if ( $type == REQUEST_OPEN ) {
    100          
343 1   33     6 $_ && utf8::encode($_) for @vars[ 0, 2 ];
344             }
345             elsif ( $type == REQUEST_CLUSTER ) {
346              
347             # undocumented; dqlite uses this to determine whether
348             # to show the last node-info piece.
349 1         2 $vars[0] = 1;
350             }
351             else {
352 4 100       9 if ( $REQUEST_HAS_SQL_AT_1{$type} ) {
353 3         7 utf8::encode( $vars[1] );
354             }
355             }
356              
357 6         25 my $packed = pack $tmpl, @vars;
358              
359 6 100       14 if ( $REQUEST_HAS_TUPLE_AT_2{$type} ) {
360 3         10 $packed .= _encode_query_tuple( @vars[ 2 .. $#vars ] );
361             }
362              
363 6         21 substr( $packed, 0, 0,
364             pack _HEADER_TEMPLATE, length($packed) >> 3, $type, );
365              
366 6         16 return $packed;
367             }
368             =head1 METHODS
369              
370             =head2 $obj = I->new()
371              
372             Instantiates I.
373              
374             =cut
375              
376             sub new {
377 1     1 1 73 return bless { _pending => q<> }, shift;
378             }
379              
380             =head2 @messages = I->feed( $BYTES )
381              
382             Give new input to I. Returns any messages parsed out of $BYTES
383             as C instances.
384              
385             =cut
386              
387             sub feed {
388 2     2 1 9 my ( $self, $input ) = @_;
389              
390 2         6 $self->{'_pending'} .= $input;
391              
392 2         5 return $self->_parse_pending();
393             }
394              
395             sub _parse_pending {
396 2     2   2 my $self = shift;
397              
398 2         3 my $pending_sr = \$self->{'_pending'};
399              
400 2         3 my @msgs;
401              
402 2         3 while (1) {
403 8 50       14 if ( !defined $self->{'_body_size'} ) {
404 8 100       16 last if length $$pending_sr < _HEADER_SIZE;
405              
406 6         19 @{$self}{ '_body_size', '_msg_type' } =
  6         12  
407             unpack( _HEADER_TEMPLATE,
408             substr( $$pending_sr, 0, _HEADER_SIZE, q<> ),
409             );
410              
411             # Body size is expressed in words, which are 8 bytes each.
412             #
413 6         9 $self->{'_body_size'} <<= 3;
414             }
415              
416 6 50       12 last if length $$pending_sr < $self->{'_body_size'};
417              
418             push @msgs, _parse_msg(
419             $self->{'_msg_type'},
420 6         14 substr( $$pending_sr, 0, $self->{'_body_size'}, q<> ),
421             );
422              
423 6         10 undef $self->{'_body_size'};
424             }
425              
426 2         6 return @msgs;
427             }
428              
429             sub _parse_msg {
430 6     6   11 my ( $type, $body ) = @_;
431              
432 6   33     10 my $metadata_ar = $RESPONSE_METADATA[$type] || do {
433             warn "Unknown Dqlite message response type: $type\n";
434             return;
435             };
436              
437 6         12 my ( $class, $tmpl, @text_indexes ) = @$metadata_ar;
438              
439 6         15 my $msg = bless [], "Protocol::Dqlite::Response::$class";
440              
441 6 100       13 if ( $type == 7 ) {
    100          
442 3         6 my $end = substr( $body, -_U64_LEN() );
443              
444 3         5 my $is_done = ( $end eq $_DQLITE_RESPONSE_ROWS_DONE );
445              
446 3 50 33     8 if ( !$is_done && $end ne $_DQLITE_RESPONSE_ROWS_PART ) {
447 0         0 warn sprintf
448             "Dqlite TableRows response ends with unexpected sequence: %v.02x\n",
449             $end;
450             }
451             else {
452 3         4 substr( $body, -_U64_LEN() ) = q<>;
453              
454 3         8 push @$msg, $is_done, _decode_response_rows($body);
455             }
456             }
457             elsif ($tmpl) {
458 2         13 @$msg = unpack $tmpl, $body;
459              
460 2   0     4 utf8::decode($_) || warn "Bad UTF-8: $_\n" for @{$msg}[@text_indexes];
  2         3  
461              
462 2 100       5 if ( $type == 3 ) { # ClusterInfo
463 1         2 my $i = 2;
464 1         5 while ( $i < @$msg ) {
465 1         3 _decode_utf8( $msg->[$i] );
466 1         2 $i += $_NODE_INFO_COUNT;
467             }
468             }
469             }
470              
471 6         11 return $msg;
472             }
473              
474             sub _decode_utf8 {
475 1 50   1   4 utf8::decode( $_[0] ) || warn "Bad UTF-8: $_[0]\n";
476             }
477              
478             sub _encode_query_tuple {
479 3     3   5 my (@type_vals) = @_;
480              
481 3         5 my $count = @type_vals >> 1;
482              
483 3         4 my ( @types, @values );
484              
485 3         8 for my $i ( 0 .. ( $count - 1 ) ) {
486 3         5 $i <<= 1;
487              
488 3         4 my $type = $type_vals[$i];
489 3         4 my $value = $type_vals[ 1 + $i ];
490              
491 3         5 push @types, $type;
492              
493 3 100       8 if ( $type == TUPLE_STRING ) {
494 1         3 utf8::encode($value);
495             }
496              
497 3         5 push @values, $value;
498             }
499              
500             my $tmpl = join(
501             q<>,
502              
503             # header
504             'C',
505             ( $count ? "C$count" : () ),
506             'x',
507             $PACK_ALIGN_U64,
508              
509             # body
510 3 100       11 map { $TUPLE_PACK[$_] } @types,
  3         8  
511             );
512              
513 3         12 return pack $tmpl, $count, @types, @values;
514             }
515              
516             # $VAR2 = "\5\0\0\0\0\0\0\0type\0\0\0\0name\0\0\0\0tbl_name\0\0\0\0\0\0\0\0rootpage\0\0\0\0\0\0\0\0sql\0\0\0\0\0003\23\3\0\0\0\0\0table\0\0\0model\0\0\0model\0\0\0\2\0\0\0\0\0\0\0CREATE TABLE model (key TEXT, value TEXT, UNIQUE(key))\0\0003\23\5\0\0\0\0\0index\0\0\0sqlite_autoindex_model_1\0\0\0\0\0\0\0\0model\0\0\0\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
517             #
518             sub _decode_response_rows {
519 3     3   4 my ($body) = @_;
520              
521 3         13 my @col_names = unpack "$PACK_U64/($PACK_STRING)", $body;
522              
523             # Ideally Perl could just *tell* us how many bytes it just read …
524             #
525 3         4 my $names_len = _U64_LEN;
526 3         6 for my $name (@col_names) {
527 8         8 $names_len += 1 + length $name;
528 8         10 _align_u64_in_place($names_len);
529             }
530 3         5 substr( $body, 0, $names_len ) = q<>;
531              
532 3         6 my @rows = _decode_response_tuples( 0 + @col_names, $body );
533              
534 3         8 utf8::decode($_) for @col_names;
535              
536 3         7 return \@col_names, \@rows;
537             }
538              
539             sub _align_u64_in_place {
540 25 100   25   47 $_[0] += ( _U64_LEN - ( $_[0] % _U64_LEN ) ) if $_[0] % _U64_LEN;
541             }
542              
543             sub _decode_response_tuples {
544 3     3   5 my ( $cols_count, $bytes ) = @_;
545              
546 3         3 my @rows;
547              
548 3         5 while ( length $bytes ) {
549 4         8 my @types = map { vec $bytes, $_, 4 } 0 .. ( $cols_count - 1 );
  13         20  
550              
551 4         7 my $unpacked_bytes = $cols_count >> 1;
552 4 100       9 $unpacked_bytes++ if ( $cols_count % 2 );
553 4         7 _align_u64_in_place($unpacked_bytes);
554              
555 4         5 substr( $bytes, 0, $unpacked_bytes ) = q<>;
556              
557             my $tmpl = join q<>,
558 4 50       7 map { $TUPLE_PACK[$_] || die "No pack for type $_" } @types;
  13         25  
559              
560 4         15 my @values = unpack "$tmpl", $bytes;
561              
562 4         7 $unpacked_bytes = 0;
563              
564             # The loop below does a few things:
565             # - Figure out how many bytes pack actually read.
566             # - UTF-8 decode as needed
567             # - Insert Perl undef wherever Dqlite indicated NULL
568             #
569 4         18 for my $t ( 0 .. $#types ) {
570 13         16 my $type = $types[$t];
571 13 100 66     27 if ( $type == TUPLE_STRING || $type == TUPLE_ISO8601 ) {
    50          
572 8         10 $unpacked_bytes += 1 + length $values[$t];
573 8         14 utf8::decode( $values[$t] );
574             }
575             elsif ( $type == TUPLE_BLOB ) {
576 0         0 $unpacked_bytes += $TUPLE_BLOB_BASE_LENGTH + length $values[$t];
577             }
578             else {
579 5 100       10 if ( $type == TUPLE_NULL ) {
580 1         3 splice @values, $t, 0, undef;
581             }
582              
583 5         6 $unpacked_bytes += $TUPLE_PACK_LENGTH[$type];
584             }
585              
586 13         16 _align_u64_in_place($unpacked_bytes);
587             }
588              
589 4         7 substr( $bytes, 0, $unpacked_bytes ) = q<>;
590              
591 4         12 push @rows, [ \@types, \@values ];
592             }
593              
594 3         6 return @rows;
595             }
596              
597             package Protocol::Dqlite::Response;
598              
599             =head1 RESPONSE CLASSES
600              
601             All of the below extend C.
602             They expose various accessors as documented below:
603              
604             =over
605              
606             =cut
607              
608             package Protocol::Dqlite::Response::FAILURE;
609              
610             =item * C: C, C
611              
612             =cut
613              
614 2     2   783 use parent -norequire => 'Protocol::Dqlite::Response';
  2         536  
  2         12  
615              
616 0     0   0 sub code { $_[0][0] }
617 0     0   0 sub message { $_[0][1] }
618              
619             package Protocol::Dqlite::Response::SERVER;
620              
621             =item * C: C, C,
622             C
623              
624             =cut
625              
626 2     2   170 use parent -norequire => 'Protocol::Dqlite::Response';
  2         4  
  2         5  
627              
628 0     0   0 sub id { $_[0][0] }
629 0     0   0 sub address { $_[0][1] }
630 0     0   0 sub role { $_[0][2] }
631              
632             package Protocol::Dqlite::Response::WELCOME;
633              
634             =item * C: (none)
635              
636             =cut
637              
638 2     2   201 use parent -norequire => 'Protocol::Dqlite::Response';
  2         4  
  2         6  
639              
640             package Protocol::Dqlite::Response::SERVERS;
641              
642             =item * C: C, C,
643             C, C; the latter 3 require an index argument, which
644             MUST be between 0 and (C - 1), inclusive.
645              
646             =cut
647              
648 2     2   95 use parent -norequire => 'Protocol::Dqlite::Response';
  2         9  
  2         6  
649              
650 1     1   11589 sub count { $_[0][0] }
651              
652             sub id {
653 1     1   27 my $which = _get_which($_[1]);
654 1         3 return $_[0][1 + $which];
655             }
656              
657             sub address {
658 1     1   25 my $which = _get_which($_[1]);
659 1         3 return $_[0][2 + $which];
660             }
661              
662             sub role {
663 1     1   21 my $which = _get_which($_[1]);
664 1         4 return $_[0][3 + $which];
665             }
666              
667             sub _get_which {
668 7     7   13 my $which = $_[0];
669 7 50       12 Carp::croak "Give node index" if !defined $which;
670 7         10 return $which;
671             }
672              
673             package Protocol::Dqlite::Response::DB;
674              
675             =item * C: C
676              
677             =cut
678              
679 2     2   463 use parent -norequire => 'Protocol::Dqlite::Response';
  2         3  
  2         7  
680              
681 1     1   325 sub id { $_[0][0] }
682              
683             package Protocol::Dqlite::Response::STMT;
684              
685             =item * C: C,
686             C, C
687              
688             =cut
689              
690 2     2   156 use parent -norequire => 'Protocol::Dqlite::Response';
  2         3  
  2         6  
691              
692 0     0   0 sub database_id { $_[0][0] }
693 0     0   0 sub statement_id { $_[0][1] }
694 0     0   0 sub params_count { $_[0][2] }
695              
696             package Protocol::Dqlite::Response::RESULT;
697              
698             =item * C: C,
699             C
700              
701             =cut
702              
703 2     2   236 use parent -norequire => 'Protocol::Dqlite::Response';
  2         3  
  2         5  
704              
705 0     0   0 sub last_row_id { $_[0][0] }
706 0     0   0 sub rows_count { $_[0][1] }
707              
708             package Protocol::Dqlite::Response::ROWS;
709              
710             =item * C:
711              
712             =over
713              
714             =item * C
715              
716             =item * C - returns a list, or a count in scalar context
717              
718             =item * C
719              
720             =item * C, C - these need an index,
721             max=(C-1)
722              
723             =back
724              
725             =cut
726              
727 2     2   198 use parent -norequire => 'Protocol::Dqlite::Response';
  2         10  
  2         8  
728              
729 3     3   2582 sub is_final { $_[0][0] }
730 3     3   1015 sub column_names { @{ $_[0][1] } }
  3         11  
731 3     3   134 sub rows_count { 0 + @{ $_[0][2] } }
  3         9  
732              
733             sub row_types {
734 2     2   881 my $which = Protocol::Dqlite::Response::SERVERS::_get_which($_[1]);
735 2         3 @{ $_[0][2][$which][0] }
  2         15  
736             }
737              
738             sub row_data {
739 2     2   1618 my $which = Protocol::Dqlite::Response::SERVERS::_get_which($_[1]);
740 2         3 @{ $_[0][2][$which][1] }
  2         8  
741             }
742              
743             package Protocol::Dqlite::Response::EMPTY;
744              
745             =item * C: (none)
746              
747             =cut
748              
749 2     2   410 use parent -norequire => 'Protocol::Dqlite::Response';
  2         3  
  2         5  
750              
751             package Protocol::Dqlite::Response::FILES;
752              
753             =item * C: C (list/count) and
754             C (takes an index)
755              
756             =cut
757              
758 2     2   132 use parent -norequire => 'Protocol::Dqlite::Response';
  2         3  
  2         8  
759              
760             sub names {
761 0     0     my @names;
762 0           for (my $i=0; $i < @_; $i += 2) {
763 0           push @names, $_[0][$i];
764             }
765              
766 0           return @names;
767             }
768              
769             sub content {
770 0     0     my $name = $_[1];
771 0 0 0       Carp::croak("Need name") if !defined $name || !length $name;
772 0           for (my $i=0; $i < @_; $i += 2) {
773 0 0         next if $_[0][$i] ne $name;
774 0           return $_[0][1 + $i];
775             }
776              
777 0           Carp::croak("No file named “$name”");
778             }
779              
780             =back
781              
782             =cut
783              
784             1;