File Coverage

blib/lib/Cassandra/Client/Protocol.pm
Criterion Covered Total %
statement 78 188 41.4
branch 18 66 27.2
condition 4 27 14.8
subroutine 17 35 48.5
pod 0 24 0.0
total 117 340 34.4


line stmt bran cond sub pod time code
1             package Cassandra::Client::Protocol;
2             our $AUTHORITY = 'cpan:TVDW';
3             $Cassandra::Client::Protocol::VERSION = '0.21';
4 13     13   231 use 5.010;
  13         50  
5 13     13   77 use strict;
  13         27  
  13         350  
6 13     13   62 use warnings;
  13         21  
  13         667  
7              
8 13     13   9127 use Encode;
  13         249564  
  13         2014  
9              
10             require Exporter;
11             our @ISA= qw(Exporter);
12              
13 13     13   7920 use Cassandra::Client::Error::Base;
  13         139  
  13         658  
14 13     13   9221 use Cassandra::Client::Error::ReadTimeoutException;
  13         48  
  13         552  
15 13     13   7329 use Cassandra::Client::Error::WriteTimeoutException;
  13         48  
  13         518  
16 13     13   7007 use Cassandra::Client::Error::UnavailableException;
  13         46  
  13         837  
17              
18 13     13   112 use constant BIGINT_SUPPORTED => eval { unpack('q>', "\0\0\0\0\0\0\0\1") };
  13         27  
  13         25  
  13         1489  
19 13     13   98 use if !BIGINT_SUPPORTED, 'Math::BigInt';
  13         27  
  13         6777  
20              
21             our (@EXPORT_OK, %EXPORT_TAGS);
22             our (%consistency_lookup, %batch_type_lookup);
23             BEGIN {
24 13     13   857 my %constants= (
25             OPCODE_ERROR => 0,
26             OPCODE_STARTUP => 1,
27             OPCODE_READY => 2,
28             OPCODE_AUTHENTICATE => 3,
29             OPCODE_OPTIONS => 5,
30             OPCODE_SUPPORTED => 6,
31             OPCODE_QUERY => 7,
32             OPCODE_RESULT => 8,
33             OPCODE_PREPARE => 9,
34             OPCODE_EXECUTE => 10,
35             OPCODE_REGISTER => 11,
36             OPCODE_EVENT => 12,
37             OPCODE_BATCH => 13,
38             OPCODE_AUTH_CHALLENGE => 14,
39             OPCODE_AUTH_RESPONSE => 15,
40             OPCODE_AUTH_SUCCESS => 16,
41              
42             RESULT_VOID => 1,
43             RESULT_ROWS => 2,
44             RESULT_SET_KEYSPACE => 3,
45             RESULT_PREPARED => 4,
46             RESULT_SCHEMA_CHANGE => 5,
47              
48             CONSISTENCY_ANY => 0,
49             CONSISTENCY_ONE => 1,
50             CONSISTENCY_TWO => 2,
51             CONSISTENCY_THREE => 3,
52             CONSISTENCY_QUORUM => 4,
53             CONSISTENCY_ALL => 5,
54             CONSISTENCY_LOCAL_QUORUM => 6,
55             CONSISTENCY_EACH_QUORUM => 7,
56             CONSISTENCY_SERIAL => 8,
57             CONSISTENCY_LOCAL_SERIAL => 9,
58             CONSISTENCY_LOCAL_ONE => 10,
59              
60             TYPE_CUSTOM => 0x00,
61             TYPE_ASCII => 0x01,
62             TYPE_BIGINT => 0x02,
63             TYPE_BLOB => 0x03,
64             TYPE_BOOLEAN => 0x04,
65             TYPE_COUNTER => 0x05,
66             TYPE_DECIMAL => 0x06,
67             TYPE_DOUBLE => 0x07,
68             TYPE_FLOAT => 0x08,
69             TYPE_INT => 0x09,
70             TYPE_TEXT => 0x0A, # deprecated/removed
71             TYPE_TIMESTAMP => 0x0B,
72             TYPE_UUID => 0x0C,
73             TYPE_VARCHAR => 0x0D,
74             TYPE_VARINT => 0x0E,
75             TYPE_TIMEUUID => 0x0F,
76             TYPE_INET => 0x10,
77             TYPE_DATE => 0x11,
78             TYPE_TIME => 0x12,
79             TYPE_SMALLINT => 0x13,
80             TYPE_TINYINT => 0x14,
81             TYPE_LIST => 0x20,
82             TYPE_MAP => 0x21,
83             TYPE_SET => 0x22,
84             TYPE_UDT => 0x30,
85             TYPE_TUPLE => 0x31,
86             );
87              
88 13         276 @EXPORT_OK= (
89             keys %constants,
90             qw/
91             pack_int unpack_int
92             pack_long
93             pack_short unpack_short
94             pack_string unpack_string
95             pack_longstring
96             pack_stringlist unpack_stringlist
97             pack_bytes unpack_bytes
98             pack_shortbytes unpack_shortbytes
99             pack_option_type
100             pack_stringmap
101             pack_stringmultimap unpack_stringmultimap
102             unpack_inet
103             unpack_char
104              
105             pack_metadata unpack_metadata
106             unpack_errordata
107             pack_queryparameters
108              
109             %consistency_lookup
110             %batch_type_lookup
111              
112             BIGINT_SUPPORTED
113             /
114             );
115              
116 13         578 %EXPORT_TAGS= (
117             constants => [ keys %constants ],
118             all => [ @EXPORT_OK ]
119             );
120              
121             %consistency_lookup= map {
122 143         232 my $key= $_;
123 143         301 $key =~ s/CONSISTENCY_//;
124 143         446 (lc $key) => $constants{$_}
125 13         150 } grep { /CONSISTENCY/ } keys %constants;
  754         1442  
126              
127 13         138 %batch_type_lookup= (
128             logged => 0,
129             unlogged => 1,
130             counter => 2,
131             );
132              
133 13         45169 constant->import( { %constants } );
134             }
135              
136             # TYPE: int
137             sub pack_int {
138 222     222 0 16079 pack('l>', $_[0])
139             }
140              
141             sub unpack_int {
142 0     0 0 0 unpack('l>', substr $_[0], 0, 4, '')
143             }
144              
145             # TYPE: long
146             sub pack_long {
147 1     1 0 1017 if (BIGINT_SUPPORTED) {
148 1         8 return pack('q>', $_[0]);
149             } else {
150             return bigint_to_bytes($_[0]);
151             }
152             }
153              
154             # TYPE: short
155             sub pack_short {
156 355     355 0 755 pack('n', $_[0])
157             }
158              
159             sub unpack_short {
160 0     0 0 0 unpack('n', substr $_[0], 0, 2, '')
161             }
162              
163             # TYPE: char
164             sub unpack_char {
165 0     0 0 0 unpack('c', substr $_[0], 0, 1, '')
166             }
167              
168             # TYPE: string
169             sub pack_string {
170 943 50   943 0 1334 if (utf8::is_utf8($_[0])) {
171 0         0 my $str= $_[0]; # copy
172 0         0 utf8::encode $str;
173 0         0 return pack('n/a', $str);
174             }
175              
176 943         1595 return pack('n/a', $_[0]);
177             }
178              
179             sub unpack_string {
180 0     0 0 0 my $length= &unpack_short;
181 0 0       0 if ($length > 0) {
182 0         0 my $string= substr($_[0], 0, $length, '');
183 0         0 utf8::decode $string;
184 0         0 return $string;
185             } else {
186 0         0 return '';
187             }
188             }
189              
190             # TYPE: longstring
191             sub pack_longstring {
192 0 0   0 0 0 if (utf8::is_utf8($_[0])) {
193 0         0 my $str= $_[0]; # copy
194 0         0 utf8::encode $str;
195 0         0 return pack('l>/a', $str);
196             }
197              
198 0         0 return pack('l>/a', $_[0]);
199             }
200              
201             # TYPE: stringlist
202             sub pack_stringlist {
203 0     0 0 0 pack_short(0+@{$_[0]}).join('', map { pack_string($_) } @{$_[0]})
  0         0  
  0         0  
  0         0  
204             }
205              
206             sub unpack_stringlist {
207 0     0 0 0 my $count= &unpack_short;
208 0         0 [ map &unpack_string, 1..$count ]
209             }
210              
211             # TYPE: bytes
212             sub pack_bytes {
213 0 0   0 0 0 if (utf8::is_utf8($_[0])) {
214 0         0 warn 'BUG: utf8 data passed to pack_bytes';
215 0         0 Encode::_utf8_off($_[0]);
216             }
217 0 0       0 defined $_[0] ? (pack_int(length($_[0])).$_[0]) : pack_int(-1)
218             }
219              
220             sub unpack_bytes {
221 0     0 0 0 my $len= &unpack_int;
222 0 0       0 if ($len > 0) {
    0          
223 0         0 return substr($_[0], 0, $len, '');
224              
225             } elsif ($len < 0) {
226 0         0 return undef;
227              
228             } else {
229 0         0 return '';
230             }
231             }
232              
233             # TYPE: shortbytes
234             sub pack_shortbytes {
235 0 0   0 0 0 if (utf8::is_utf8($_[0])) {
236 0         0 warn 'BUG: utf8 data passed to pack_shortbytes';
237 0         0 Encode::_utf8_off($_[0]);
238             }
239 0 0       0 defined $_[0] ? (pack_short(length($_[0])).$_[0]) : pack_short(-1)
240             }
241              
242             sub unpack_shortbytes {
243 0     0 0 0 my $len= &unpack_short;
244 0 0       0 if ($len > 0) {
    0          
245 0         0 return substr($_[0], 0, $len, '');
246              
247             } elsif ($len < 0) {
248 0         0 return undef;
249              
250             } else {
251 0         0 return '';
252             }
253             }
254              
255             # TYPE: inet
256             sub unpack_inet {
257 0     0 0 0 my $length= unpack('C', substr($_[0], 0, 1, ''));
258 0         0 my $tmp_val= substr($_[0], 0, $length, '');
259              
260 0         0 my $addr;
261 0 0       0 if ($length == 4) {
262 0         0 $addr= join('.', unpack('CCCC', $tmp_val));
263             } else {
264 0         0 $addr= join(':', unpack('(H4)[8]', $tmp_val));
265             # Simplify the V6 address
266 0         0 $addr =~ s/\b0+(\d+)\b/$1/g;
267 0         0 $addr =~ s/\b0(:0)+\b/:/;
268 0         0 $addr =~ s/:::/::/;
269             }
270 0         0 return $addr;
271             }
272              
273             # TYPE: option_type
274             sub pack_option_type {
275 350     350 0 390 my ($type)= @_;
276 350         419 my ($id, @value)= @$type;
277 350 100 100     581 if ($id == TYPE_CUSTOM) {
    100          
    100          
    100          
    100          
    50          
278 4         8 return pack_short($id).pack_string($value[0]);
279             } elsif ($id < 0x20) {
280 319         384 return pack_short($id);
281             } elsif ($id == TYPE_LIST || $id == TYPE_SET) {
282 13         19 return pack_short($id).pack_option_type($value[0]);
283             } elsif ($id == TYPE_MAP) {
284 9         17 return pack_short($id).pack_option_type($value[0]).pack_option_type($value[1]);
285             } elsif ($id == TYPE_UDT) {
286 2         5 my $out= pack_short($id).pack_string($value[0]).pack_string($value[1]);
287 2         4 my @fields= @{$value[2]};
  2         5  
288 2         5 $out .= pack_short(0+@fields);
289 2         4 for my $field (@fields) {
290 2         4 $out .= pack_string($field->[0]).pack_option_type($field->[1]);
291             }
292 2         6 return $out;
293             } elsif ($id == TYPE_TUPLE) {
294 3         5 my @fields= @{$value[0]};
  3         6  
295 3         5 my $out= pack_short($id).pack_short(0+@fields);
296 3         7 $out .= pack_option_type($_) for @fields;
297 3         11 return $out;
298             } else {
299 0         0 die 'Unable to pack_option_type for type '.$id;
300             }
301             }
302              
303             # TYPE: stringmap
304             sub pack_stringmap {
305 0     0 0 0 my $pairs= '';
306 0         0 my $count= 0;
307 0         0 for my $key (sort keys %{$_[0]}) {
  0         0  
308 0         0 $pairs .= pack_string($key).pack_string($_[0]{$key});
309 0         0 $count++;
310             }
311 0         0 return pack_short($count).$pairs;
312             }
313              
314             # TYPE: stringmultimap
315             sub pack_stringmultimap {
316 0     0 0 0 my $pairs= '';
317 0         0 my $count= 0;
318 0         0 for my $key (sort keys %{$_[0]}) {
  0         0  
319 0         0 $pairs .= pack_string($key).pack_stringlist($_[0]{$key});
320 0         0 $count++;
321             }
322 0         0 return pack_short($count).$pairs;
323             }
324              
325             sub unpack_stringmultimap {
326 0     0 0 0 my $count= &unpack_short;
327 0         0 my $result= {};
328 0         0 for (1..$count) {
329 0         0 my $key= &unpack_string;
330 0         0 $result->{$key}= &unpack_stringlist;
331             }
332 0         0 return $result;
333             }
334              
335             # Metadata
336             sub pack_metadata {
337 96     96 0 236335 my ($protoversion, $is_result, $metadata)= @_;
338 96 50 33     405 die "pack_metadata can only encode v4 results" unless $protoversion == 4 and $is_result;
339 96         139 my $columns= $metadata->{columns};
340 96         125 my $paging_state= $metadata->{paging_state};
341              
342 96 50       253 my $flags= ($columns ? 0 : 4) | (defined($paging_state) ? 2 : 0);
    50          
343              
344 96         176 my $out= pack_int($flags);
345 96 50       218 $out .= pack_int($columns ? (0+@$columns) : 0);
346 96 50       243 $out .= pack_bytes($paging_state) if $flags & 2;
347 96 50       174 unless ($flags & 4) {
348 96         161 for my $column (@$columns) {
349 311         457 $out .= pack_string($column->[0]).pack_string($column->[1]);
350 311         430 $out .= pack_string($column->[2]).pack_option_type($column->[3]);
351             }
352             }
353              
354 96         209 return $out;
355             }
356              
357             # Query parameters
358             sub pack_queryparameters {
359 0     0 0   my ($consistency, $skip_metadata, $page_size, $paging_state, $timestamp, $row)= @_;
360              
361 0   0       my $has_row= defined($row) && length($row);
362 0   0       my $flags= (
      0        
      0        
      0        
      0        
363             0
364             | (($has_row && 0x01) || 0)
365             | (($skip_metadata && 0x02) || 0)
366             | (($page_size && 0x04) || 0)
367             | (($paging_state && 0x08) || 0)
368             | (($timestamp && 0x20) || 0)
369             );
370              
371             return (
372 0 0 0       pack('nC', $consistency, $flags)
    0          
    0          
373             . ($row || '')
374             . ($page_size ? pack('l>', $page_size) : '')
375             . ($paging_state ? pack('l>/a', $paging_state) : '')
376             . ($timestamp ? (BIGINT_SUPPORTED ? pack('q>', $timestamp) : bigint_to_bytes($timestamp)) : '')
377             );
378             }
379              
380             sub unpack_errordata {
381 0     0 0   my $code= &unpack_int;
382              
383 0           my %error;
384 0           $error{code}= $code;
385 0           $error{message}= &unpack_string;
386 0   0       $error{is_timeout}= ( $code == 0x1001 || $code == 0x1100 || $code == 0x1200 );
387              
388 0 0         if ($code == 0x1000) {
    0          
    0          
389             # Unavailable
390 0           $error{cl}= &unpack_short;
391 0           $error{required}= &unpack_int;
392 0           $error{alive}= &unpack_int;
393 0           return Cassandra::Client::Error::UnavailableException->new(%error);
394             } elsif ($code == 0x1100) {
395             # Write timeout
396 0           $error{cl}= &unpack_short;
397 0           $error{received}= &unpack_int;
398 0           $error{blockfor}= &unpack_int;
399 0           $error{write_type}= &unpack_string;
400 0           return Cassandra::Client::Error::WriteTimeoutException->new(%error);
401             } elsif ($code == 0x1200) {
402             # Read timeout
403 0           $error{cl}= &unpack_short;
404 0           $error{received}= &unpack_int;
405 0           $error{blockfor}= &unpack_int;
406 0           $error{data_present}= &unpack_char;
407 0           return Cassandra::Client::Error::ReadTimeoutException->new(%error);
408             }
409              
410 0           return Cassandra::Client::Error::Base->new(%error);
411             }
412              
413             # Support for 32bit perl
414             sub bigint_to_bytes {
415 0     0 0   my $mb= Math::BigInt->new($_[0]);
416 0 0         if ($_[0] !~ /^-?[0-9\.E]+$/i) { # Idk, approximate it
417 0           warn "Argument $_[0] isn't numeric";
418             }
419 0   0       my $negative= $mb->is_neg && $mb != 0;
420 0 0         if ($negative) {
421 0           $mb *= -1; # Flips the bits, adds one
422 0           $mb -= 1; # Removes that one
423             }
424              
425 0           my $hex= $mb->as_hex;
426 0           $hex =~ s/^0x//;
427 0           my $bytes= pack('H*', substr(("0"x16).$hex, -16));
428 0 0         if ($negative) {
429 0           $bytes= ~$bytes; # Flip those bits back
430             }
431              
432 0           return $bytes;
433             }
434              
435             1;
436              
437             __END__