File Coverage

blib/lib/Protocol/CassandraCQL/ColumnMeta.pm
Criterion Covered Total %
statement 106 106 100.0
branch 21 32 65.6
condition 9 15 60.0
subroutine 17 17 100.0
pod 11 11 100.0
total 164 181 90.6


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, 2013-2014 -- leonerd@leonerd.org.uk
5              
6             package Protocol::CassandraCQL::ColumnMeta;
7              
8 5     5   529 use strict;
  5         17  
  5         175  
9 5     5   23 use warnings;
  5         6  
  5         189  
10              
11             our $VERSION = '0.12';
12              
13 5     5   24 use Carp;
  5         7  
  5         348  
14              
15 5     5   1159 use Protocol::CassandraCQL qw( :rowflags );
  5         10  
  5         820  
16 5     5   2573 use Protocol::CassandraCQL::Type;
  5         15  
  5         4807  
17              
18             =head1 NAME
19              
20             C - stores the column metadata of a Cassandra CQL query
21              
22             =head1 DESCRIPTION
23              
24             Objects in this class interpret the column metadata from a message frame
25             containing a C response to a query giving C or
26             C. It provides lookup of column names and type information,
27             and provides a convenient accessor to the encoding and decoding support
28             functions, allowing encoding of bytestrings from perl data when executing a
29             prepared statement, and decoding of bytestrings to perl data when obtaining
30             query results.
31              
32             It is also subclassed as L.
33              
34             =cut
35              
36             =head1 CONSTRUCTORS
37              
38             =cut
39              
40             =head2 $meta = Protocol::CassandraCQL::ColumnMeta->from_frame( $frame, $version )
41              
42             Returns a new column metadata object initialised from the given message frame
43             at the given CQL version number. (Version will default to 1 if not supplied,
44             but this may become a required parameter in a future version).
45              
46             =cut
47              
48             sub from_frame
49             {
50 14     14 1 20 my $class = shift;
51 14         20 my ( $frame, $version ) = @_;
52              
53 14 100       36 defined $version or $version = 1;
54              
55 14         35 my $self = bless {}, $class;
56              
57 14         55 $self->{columns} = \my @columns;
58              
59 14         50 my $flags = $frame->unpack_int;
60 14         36 my $n_columns = $frame->unpack_int;
61              
62 14         25 my $has_gts = $flags & ROWS_HAS_GLOBALTABLESPEC;
63              
64 14   100     58 my $has_paging = ( $version > 1 ) && ( $flags & ROWS_HAS_MORE_PAGES );
65 14   100     49 my $no_metadata = ( $version > 1 ) && ( $flags & ROWS_NO_METADATA );
66              
67 14 100       32 if( $has_paging ) {
68 2         10 $self->{paging_state} = $frame->unpack_bytes;
69             }
70              
71 14 100       31 if( $no_metadata ) {
72 2         10 push @columns, undef for 1 .. $n_columns;
73             }
74             else {
75 12 50       176 my @gts = $has_gts ? ( $frame->unpack_string, $frame->unpack_string )
76             : ();
77              
78 12         167 foreach ( 1 .. $n_columns ) {
79 22 50       66 my @keyspace_table = $has_gts ? @gts : ( $frame->unpack_string, $frame->unpack_string );
80 22         50 my $colname = $frame->unpack_string;
81 22         342 my $type = Protocol::CassandraCQL::Type->from_frame( $frame );
82              
83 22         71 my @col = ( @keyspace_table, $colname, undef, $type );
84              
85 22         63 push @columns, \@col;
86             }
87              
88 12         48 $self->_set_shortnames;
89             }
90              
91 14         46 return $self;
92             }
93              
94             =head2 $meta = Protocol::CassandraCQL::ColumnMeta->new( %args )
95              
96             Returns a new column metadata object initialised directly from the given
97             column data. This constructor is intended for use by unit test scripts, to
98             create metadata directly from mocked connection objects or similar.
99              
100             It takes the following named arguments:
101              
102             =over 8
103              
104             =item columns => ARRAY[ARRAY[STR, STR, STR, STR]]
105              
106             An ARRAY reference containing the data about individual columns. Each row is
107             represented by an ARRAY reference containing four strings; giving the three
108             components of its name, and the name of its type:
109              
110             [ $keyspace, $table, $column, $typename ]
111              
112             =back
113              
114             =cut
115              
116             sub new
117             {
118 3     3 1 1122 my $class = shift;
119 3         7 my %args = @_;
120              
121 3         12 my $self = bless {}, $class;
122              
123 3         15 $self->{columns} = \my @columns;
124              
125 3         5 foreach my $c ( @{ $args{columns} } ) {
  3         11  
126 6         29 push @columns, [
127 6         9 @{$c}[0,1,2], # name
128             undef, # shortname
129             Protocol::CassandraCQL::Type->from_name( $c->[3] ),
130             ];
131             }
132              
133 3         11 $self->_set_shortnames;
134              
135 3         14 return $self;
136             }
137              
138             sub _set_shortnames
139             {
140 15     15   20 my $self = shift;
141              
142 15         26 my $columns = $self->{columns};
143              
144 15         49 foreach my $idx ( 0 .. $#$columns ) {
145 28         35 my $c = $columns->[$idx];
146 28         26 my @names;
147              
148 28         88 my $name = "$c->[0].$c->[1].$c->[2]";
149 28         38 push @names, $name;
150              
151 28         59 $name = "$c->[1].$c->[2]";
152 28 50       45 push @names, $name if 1 == grep { "$_->[1].$_->[2]" eq $name } @$columns;
  58         176  
153              
154 28         35 $name = $c->[2];
155 28 50       45 push @names, $name if 1 == grep { $_->[2] eq $name } @$columns;
  58         128  
156              
157 28         43 $c->[3] = $names[-1];
158 28         223 $self->{name_to_col}{$_} = $idx for @names;
159             }
160             }
161              
162             =head1 METHODS
163              
164             =cut
165              
166             =head2 $n = $meta->columns
167              
168             Returns the number of columns
169              
170             =cut
171              
172             sub columns
173             {
174 38     38 1 4166 my $self = shift;
175 38         43 return scalar @{ $self->{columns} };
  38         123  
176             }
177              
178             =head2 $name = $meta->column_name( $idx )
179              
180             =head2 ( $keyspace, $table, $column ) = $meta->column_name( $idx )
181              
182             Returns the name of the column at the given (0-based) index; either as three
183             separate strings, or all joined by ".".
184              
185             =cut
186              
187             sub column_name
188             {
189 7     7 1 48 my $self = shift;
190 7         127 my ( $idx ) = @_;
191              
192 7 50 33     25 croak "No such column $idx" unless $idx >= 0 and $idx < @{ $self->{columns} };
  7         37  
193 7         11 my @n = @{ $self->{columns}[$idx] }[0..2];
  7         120  
194              
195 7 100       55 return @n if wantarray;
196 2         13 return join ".", @n;
197             }
198              
199             =head2 $name = $meta->column_shortname( $idx )
200              
201             Returns the short name of the column; which will be just the column name
202             unless it requires the table or keyspace name as well to make it unique within
203             the set.
204              
205             =cut
206              
207             sub column_shortname
208             {
209 28     28 1 38 my $self = shift;
210 28         27 my ( $idx ) = @_;
211              
212 28 50 33     72 croak "No such column $idx" unless $idx >= 0 and $idx < @{ $self->{columns} };
  28         93  
213 28         547 return $self->{columns}[$idx][3];
214             }
215              
216             =head2 $type = $meta->column_type( $idx )
217              
218             Returns the type of the column at the given index as an instance of
219             L.
220              
221             =cut
222              
223             sub column_type
224             {
225 52     52 1 72 my $self = shift;
226 52         62 my ( $idx ) = @_;
227              
228 52 50 33     131 croak "No such column $idx" unless $idx >= 0 and $idx < @{ $self->{columns} };
  52         220  
229 52         357 return $self->{columns}[$idx][4];
230             }
231              
232             =head2 $idx = $meta->find_column( $name )
233              
234             Returns the index of the given named column. The name may be given as
235             C, or C or C if they are unique
236             within the set. Returns C if no such column exists.
237              
238             =cut
239              
240             sub find_column
241             {
242 6     6 1 12 my $self = shift;
243 6         11 my ( $name ) = @_;
244              
245 6         30 return $self->{name_to_col}{$name};
246             }
247              
248             =head2 @bytes = $meta->encode_data( @data )
249              
250             Returns a list of encoded bytestrings from the given data according to the
251             type of each column. Checks each value is valid; if not throws an exception
252             explaining which column failed and why.
253              
254             An exception is thrown if the wrong number of values is passed.
255              
256             =cut
257              
258             sub encode_data
259             {
260 4     4 1 1498 my $self = shift;
261 4         11 my @data = @_;
262              
263 4         6 my $n = @{ $self->{columns} };
  4         13  
264 4 50       14 croak "Too many values" if @data > $n;
265 4 50       15 croak "Not enough values" if @data < $n;
266              
267 4         14 foreach my $i ( 0 .. $#data ) {
268 9 100       17 my $e = $self->column_type( $i )->validate( $data[$i] ) or next;
269              
270 2         15 croak "Cannot encode ".$self->column_shortname( $i ).": $e";
271             }
272              
273 2 50       10 return map { defined $data[$_] ? $self->column_type( $_ )->encode( $data[$_] ) : undef }
  6         63  
274             0 .. $n-1;
275             }
276              
277             =head2 @data = $meta->decode_data( @bytes )
278              
279             Returns a list of decoded data from the given encoded bytestrings according to
280             the type of each column.
281              
282             =cut
283              
284             sub decode_data
285             {
286 9     9 1 1284 my $self = shift;
287 9         17 my @bytes = @_;
288              
289 9 50       19 return map { defined $bytes[$_] ? $self->column_type( $_ )->decode( $bytes[$_] ) : undef }
  17         158  
290             0 .. $#bytes;
291             }
292              
293             =head2 $bytes = $meta->paging_state
294              
295             Returns the CQLv2+ paging state, if it was contained in the given frame. This
296             would be returned in an C message to a query or execute request
297             that requested paging.
298              
299             =cut
300              
301             sub paging_state
302             {
303 2     2 1 4 my $self = shift;
304 2         10 return $self->{paging_state};
305             }
306              
307             =head2 $bool = $meta->has_metadata
308              
309             Returns a boolean indicating whether the column metadata (field names and
310             types) is actually defined. Normally this would be true, except if the object
311             is an instance of L returned by executing a
312             prepared statement with metadata specifically disabled.
313              
314             =cut
315              
316             sub has_metadata
317             {
318 15     15 1 23 my $self = shift;
319 15         69 return defined $self->{columns}[0];
320             }
321              
322             =head1 SPONSORS
323              
324             This code was paid for by
325              
326             =over 2
327              
328             =item *
329              
330             Perceptyx L
331              
332             =item *
333              
334             Shadowcat Systems L
335              
336             =back
337              
338             =head1 AUTHOR
339              
340             Paul Evans
341              
342             =cut
343              
344             0x55AA;