File Coverage

blib/lib/Protocol/CassandraCQL/Frames.pm
Criterion Covered Total %
statement 117 120 97.5
branch 59 82 71.9
condition 16 39 41.0
subroutine 20 20 100.0
pod 11 11 100.0
total 223 272 81.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2014 -- leonerd@leonerd.org.uk
5              
6             package Protocol::CassandraCQL::Frames;
7              
8 3     3   25754 use strict;
  3         6  
  3         137  
9 3     3   14 use warnings;
  3         5  
  3         136  
10              
11             our $VERSION = '0.12';
12              
13 3     3   14 use Exporter 'import';
  3         4  
  3         232  
14             our @EXPORT_OK = qw(
15             build_startup_frame
16             build_credentials_frame
17             build_query_frame
18             build_prepare_frame
19             build_execute_frame
20             build_register_frame
21              
22             parse_error_frame
23             parse_authenticate_frame
24             parse_supported_frame
25             parse_result_frame
26             parse_event_frame
27             );
28              
29             our %EXPORT_TAGS;
30             $EXPORT_TAGS{all} = [ @EXPORT_OK ];
31              
32 3     3   14 use Carp;
  3         5  
  3         197  
33              
34 3     3   495 use Protocol::CassandraCQL qw( :queryflags :results );
  3         7  
  3         597  
35 3     3   628 use Protocol::CassandraCQL::Frame;
  3         5  
  3         75  
36 3     3   1251 use Protocol::CassandraCQL::Result;
  3         9  
  3         123  
37              
38             # The highest version we know about
39 3     3   17 use constant MAX_VERSION => 2;
  3         5  
  3         4419  
40              
41             =head1 NAME
42              
43             C - build or parse frame bodies for specific
44             message types
45              
46             =head1 SYNOPSIS
47              
48             use Protocol::CassandraCQL qw( build_frame );
49             use Protocol::CassandraCQL::Frames qw( build_query_frame );
50              
51             my $bytes = build_frame( 0x01, 0, $streamid, OPCODE_QUERY,
52             build_query_frame( 1,
53             cql => "CQL STRING",
54             consistency => $consistency
55             )->bytes
56             );
57              
58             =head1 DESCRIPTION
59              
60             This module provides a number of convenient functions to build and parse frame
61             bodies for specific kinds of C message. Each should be paired with a call
62             to C or C with the appropriate opcode constant, or
63             invoked after C or C has received a frame with the
64             appropriate opcode.
65              
66             Each C function takes as its first argument the C protocol
67             version (the value that will be passed to C or C).
68             This value is used to ensure all the correct information is present in the
69             frame body, and that no optional parameters are passed that the chosen version
70             of the protocol cannot support.
71              
72             =cut
73              
74             =head1 FUNCTIONS
75              
76             =cut
77              
78             =head2 $frame = build_startup_frame( $version, options => \%options )
79              
80             Builds the frame for an C message. Takes a reference to a hash
81             of named options. These options should include C.
82              
83             =cut
84              
85             sub build_startup_frame
86             {
87 2     2 1 14 my ( $version, %params ) = @_;
88              
89 2 50 33     23 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
90              
91 2 50       15 defined( my $options = $params{options} ) or croak "Need 'options'";
92              
93 2 50       5 croak "'CQL_VERSION' missing" unless defined $options->{CQL_VERSION};
94              
95 2         34 return Protocol::CassandraCQL::Frame->new
96             ->pack_string_map( $options );
97             }
98              
99             =head2 $frame = build_credentials_frame( $version, credentials => \%credentials )
100              
101             Builds the frame for an C message. Takes a reference to a
102             hash of credentials, the exact keys of which will depend on the authenticator
103             returned by the C message.
104              
105             =cut
106              
107             sub build_credentials_frame
108             {
109 2     2 1 1232 my ( $version, %params ) = @_;
110              
111             # OPCODE_CREDENTIALS is only v1
112 2 100 66     216 croak "Unsupported version" if $version < 1 or $version > 1;
113              
114 1 50       6 defined( my $credentials = $params{credentials} ) or croak "Need 'credentials'";
115              
116 1         7 return Protocol::CassandraCQL::Frame->new
117             ->pack_string_map( $credentials );
118             }
119              
120             =head2 $frame = build_query_frame( $version, cql => $cql, QUERY_PARAMS )
121              
122             Builds the frame for an C message. Takes the CQL string and the
123             query parameters.
124              
125             C contains the following keys:
126              
127             =over 4
128              
129             =item consistency => INT
130              
131             The consistency level. (required)
132              
133             =item values => ARRAY of STRING
134              
135             The encoded byte values of the bind parameters (optional, v2+ only)
136              
137             =item skip_metadata => BOOL
138              
139             If true, sets the C flag. (optional, v2+ only)
140              
141             =item page_size => INT
142              
143             The paging size (optional, v2+ only)
144              
145             =item paging_state => STRING
146              
147             The paging state from the previous result to a query or execute. (optional,
148             v2+ only)
149              
150             =item serial_consistency => INT
151              
152             The consistency level for CAS serialisation operations (optional, v2+ only)
153              
154             =back
155              
156             =cut
157              
158             # Shared by QUERY and EXECUTE frames at version 2
159             sub _pack_query_params
160             {
161 9     9   22 my ( $version, $frame, %params ) = @_;
162              
163 9 50       22 defined( my $consistency = $params{consistency} ) or croak "Need 'consistency'";
164              
165 9         30 $frame->pack_short( $consistency );
166              
167 9 100       19 if( $version < 2 ) {
168             defined $params{$_} and croak "Cannot set '$_' for version 1"
169 2   66     120 for qw( values page_size paging_state serial_consistency );
170 1         3 return $frame;
171             }
172              
173 7         8 my $flags = 0;
174              
175 7         10 my $values = $params{values};
176 7         8 my $page_size = $params{page_size};
177 7         7 my $paging_state = $params{paging_state};
178 7         9 my $ser_cons = $params{serial_consistency};
179              
180 7 100       15 $flags |= QUERY_VALUES if $values;
181 7 100       18 $flags |= QUERY_SKIP_METADATA if $params{skip_metadata};
182 7 100       14 $flags |= QUERY_PAGE_SIZE if defined $page_size;
183 7 100       12 $flags |= QUERY_WITH_PAGING_STATE if defined $paging_state;
184 7 100       25 $flags |= QUERY_WITH_SERIAL_CONSISTENCY if defined $ser_cons;
185              
186 7         23 $frame->pack_byte( $flags );
187              
188 7 100       11 if( $values ) {
189 2         7 $frame->pack_short( scalar @$values );
190 2         11 $frame->pack_bytes( $_ ) for @$values;
191             }
192              
193 7 100       26 if( defined $page_size ) {
194 2         7 $frame->pack_int( $page_size );
195             }
196              
197 7 100       12 if( defined $paging_state ) {
198 1         4 $frame->pack_bytes( $paging_state );
199             }
200              
201 7 100       16 if( defined $ser_cons ) {
202 1         3 $frame->pack_short( $ser_cons );
203             }
204              
205 7         17 return $frame;
206             }
207              
208             sub build_query_frame
209             {
210 8     8 1 3157 my ( $version, %params ) = @_;
211              
212 8 50 33     41 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
213              
214 8 50       20 defined( my $cql = $params{cql} ) or croak "Need 'cql'";
215              
216 8         25 my $frame = Protocol::CassandraCQL::Frame->new
217             ->pack_lstring( $cql );
218              
219 8         24 _pack_query_params( $version, $frame, %params );
220              
221 7         20 return $frame;
222             }
223              
224             =head2 $frame = build_prepare_frame( $version, cql => $cql )
225              
226             Builds the frame for an C message. Takes the CQL string.
227              
228             =cut
229              
230             sub build_prepare_frame
231             {
232 1     1 1 361 my ( $version, %params ) = @_;
233              
234 1 50 33     9 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
235              
236 1 50       5 defined( my $cql = $params{cql} ) or croak "Need 'cql'";
237              
238 1         6 return Protocol::CassandraCQL::Frame->new
239             ->pack_lstring( $cql );
240             }
241              
242             =head2 $frame = build_execute_frame( $version, id => $id, QUERY_PARAMS )
243              
244             Builds the frame for an C message. Takes the prepared
245             statement ID, and the query parameters. C is as for
246             C, except that the C key is required and permitted
247             even at protocol version 1.
248              
249             =cut
250              
251             sub build_execute_frame
252             {
253 3     3 1 868 my ( $version, %params ) = @_;
254              
255 3 50 33     22 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
256              
257 3 50       41 defined( my $id = $params{id} ) or croak "Need 'id'";
258              
259             # v1 had a different layout
260 3 50       11 defined( my $values = $params{values} ) or croak "Need 'values'";
261 3 50       7 defined( my $consistency = $params{consistency} ) or croak "Need 'consistency'";
262              
263 3         13 my $frame = Protocol::CassandraCQL::Frame->new
264             ->pack_short_bytes( $id );
265              
266 3 100       9 if( $version == 1 ) {
267             defined $params{$_} and croak "Cannot set '$_' for version 1"
268 2   66     180 for qw( page_size paging_state serial_consistency );
269 1         3 $frame->pack_short( scalar @$values );
270 1         4 $frame->pack_bytes( $_ ) for @$values;
271 1         6 $frame->pack_short( $consistency );
272             }
273             else {
274 1         6 _pack_query_params( $version, $frame, %params );
275             }
276              
277 2         9 return $frame;
278             }
279              
280             =head2 $frame = build_register_frame( $version, events => \@events )
281              
282             Builds the frame for an C message. Takes an ARRAY reference
283             of strings giving the event names.
284              
285             =cut
286              
287             sub build_register_frame
288             {
289 1     1 1 381 my ( $version, %params ) = @_;
290              
291 1 50 33     9 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
292              
293 1 50       4 defined( my $events = $params{events} ) or croak "Need 'events'";
294              
295 1         11 return Protocol::CassandraCQL::Frame->new
296             ->pack_string_list( $events );
297             }
298              
299             =head2 ( $err, $message ) = parse_error_frame( $version, $frame )
300              
301             Parses the frame from an C message. Returns an error code value
302             and a string message.
303              
304             =cut
305              
306             sub parse_error_frame
307             {
308 1     1 1 2 my ( $version, $frame ) = @_;
309              
310 1 50 33     7 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
311              
312 1         15 return ( $frame->unpack_int,
313             $frame->unpack_string );
314             }
315              
316             =head2 ( $authenticator ) = parse_authenticate_frame( $version, $frame )
317              
318             Parses the frame from an C message. Returns the
319             authenticator name as a string.
320              
321             =cut
322              
323             sub parse_authenticate_frame
324             {
325 1     1 1 2 my ( $version, $frame ) = @_;
326              
327 1 50 33     8 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
328              
329 1         3 return ( $frame->unpack_string );
330             }
331              
332             =head2 ( $options ) = parse_supported_frame( $version, $frame )
333              
334             Parses the frame from an C message. Returns a HASH reference
335             mapping option names to ARRAYs of supported values.
336              
337             =cut
338              
339             sub parse_supported_frame
340             {
341 1     1 1 2 my ( $version, $frame ) = @_;
342              
343 1 50 33     9 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
344              
345 1         5 return $frame->unpack_string_multimap;
346             }
347              
348             =head2 ( $type, $result ) = parse_result_frame( $version, $frame )
349              
350             Parses the frame from an C message. Returns a type value (one
351             of the C constants), and a value whose interpretation depends on the
352             type.
353              
354             =over 4
355              
356             =item * RESULT_VOID
357              
358             C<$result> is C. (This is returned by data modification queries such as
359             C, C and C).
360              
361             =item * RESULT_ROWS
362              
363             C<$result> is an instance of L containing the
364             row data. (This is returned by C
365              
366             =item * RESULT_SET_KEYSPACE
367              
368             C<$result> is a string containing the new keyspace name. (This is returned by
369             C queries).
370              
371             =item * RESULT_PREPARED
372              
373             C<$result> is an ARRAY reference containing the query ID as a string, and the
374             bind parameters' metadata as an instance of
375             L. For v2+ this will also return the
376             result metadata as another C instance.
377              
378             =item * RESULT_SCHEMA_CHANGE
379              
380             C<$result> is an ARRAY reference containing three strings, giving the type
381             of change, the keyspace, and the table name. (This is returned by data
382             definition queries such as C, C and C).
383              
384             =back
385              
386             If any other type is encountered, C<$result> will be the C<$frame> object
387             itself.
388              
389             =cut
390              
391             sub parse_result_frame
392             {
393 7     7 1 10 my ( $version, $frame ) = @_;
394              
395 7 50 33     34 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
396              
397 7         86 my $type = $frame->unpack_int;
398              
399 7 100       32 if( $type == RESULT_VOID ) {
    100          
    100          
    100          
    50          
400 1         2 return ( $type, undef );
401             }
402             elsif( $type == RESULT_ROWS ) {
403 2         12 return ( $type, Protocol::CassandraCQL::Result->from_frame( $frame, $version ) );
404             }
405             elsif( $type == RESULT_SET_KEYSPACE ) {
406 1         3 return ( $type, $frame->unpack_string );
407             }
408             elsif( $type == RESULT_PREPARED ) {
409 2         7 my $id = $frame->unpack_short_bytes;
410 2         9 my $params_meta = Protocol::CassandraCQL::ColumnMeta->from_frame( $frame );
411              
412 2 100       5 if( $version < 2 ) {
413 1         6 return ( $type, [
414             $id,
415             $params_meta
416             ] );
417             }
418              
419 1         5 return ( $type, [
420             $id,
421             $params_meta,
422             Protocol::CassandraCQL::ColumnMeta->from_frame( $frame )
423             ] );
424             }
425             elsif( $type == RESULT_SCHEMA_CHANGE ) {
426 1         4 return ( $type, [ map { $frame->unpack_string } 1 .. 3 ] );
  3         31  
427             }
428             else {
429 0         0 return ( $type, $frame );
430             }
431             }
432              
433             =head2 ( $event, @args ) = parse_event_frame( $version, $frame )
434              
435             Parses the frame from an C message. Returns the event name and a
436             list of its arguments; which will vary depending on the event name.
437              
438             =over 4
439              
440             =item * TOPOLOGY_CHANGE
441              
442             C<@args> will contain the change type string and a node inet address
443              
444             =item * STATUS_CHANGE
445              
446             C<@args> will contain the status type string and a node inet address
447              
448             =item * SCHEMA_CHANGE
449              
450             C<@args> will contain three strings, containing the change type, keyspace,
451             and table name
452              
453             =back
454              
455             If the event name is unrecognised, C<@args> will return just the C<$frame>
456             object itself.
457              
458             =cut
459              
460             sub parse_event_frame
461             {
462 2     2 1 3 my ( $version, $frame ) = @_;
463              
464 2 50 33     12 croak "Unsupported version" if $version < 1 or $version > MAX_VERSION;
465              
466 2         6 my $event = $frame->unpack_string;
467              
468 2 100       41 if( $event eq "TOPOLOGY_CHANGE" ) {
    50          
    50          
469 1         3 return ( $event, $frame->unpack_string, $frame->unpack_inet );
470             }
471             elsif( $event eq "STATUS_CHANGE" ) {
472 0         0 return ( $event, $frame->unpack_string, $frame->unpack_inet );
473             }
474             elsif( $event eq "SCHEMA_CHANGE" ) {
475 1         3 return ( $event, map { $frame->unpack_string } 1 .. 3 );
  3         31  
476             }
477             else {
478 0           return ( $event, $frame );
479             }
480             }
481              
482             =head1 SPONSORS
483              
484             This code was paid for by
485              
486             =over 2
487              
488             =item *
489              
490             Perceptyx L
491              
492             =item *
493              
494             Shadowcat Systems L
495              
496             =back
497              
498             =head1 AUTHOR
499              
500             Paul Evans
501              
502             =cut
503              
504             0x55AA;