File Coverage

blib/lib/Protocol/Redis/Faster.pm
Criterion Covered Total %
statement 67 69 97.1
branch 30 32 93.7
condition n/a
subroutine 8 8 100.0
pod 4 4 100.0
total 109 113 96.4


line stmt bran cond sub pod time code
1             package Protocol::Redis::Faster;
2              
3 1     1   65990 use strict;
  1         9  
  1         28  
4 1     1   5 use warnings;
  1         2  
  1         21  
5 1     1   5 use Carp ();
  1         9  
  1         22  
6              
7 1     1   439 use parent 'Protocol::Redis';
  1         310  
  1         5  
8              
9             our $VERSION = '0.003';
10              
11             my %simple_types = ('+' => 1, '-' => 1, ':' => 1);
12              
13             sub encode {
14 11     11 1 35 my $self = shift;
15              
16 11         19 my $encoded = '';
17 11         31 while (@_) {
18 17         27 my $message = shift;
19              
20             # Order optimized for client encoding;
21             # client commands are sent as arrays of bulk strings
22              
23             # Bulk strings
24 17 100       49 if ($message->{type} eq '$') {
    100          
    50          
25 9 100       22 if (defined $message->{data}) {
26 7         25 $encoded .= '$' . length($message->{data}) . "\r\n" . $message->{data} . "\r\n";
27             }
28             else {
29 2         6 $encoded .= '$-1' . "\r\n";
30             }
31             }
32              
33             # Arrays
34             elsif ($message->{type} eq '*') {
35 5 100       13 if (defined $message->{data}) {
36 4         8 $encoded .= '*' . scalar(@{$message->{data}}) . "\r\n";
  4         12  
37 4         7 unshift @_, @{$message->{data}};
  4         44  
38             }
39             else {
40 1         4 $encoded .= '*-1' . "\r\n";
41             }
42             }
43              
44             # Simple strings, errors, and integers
45             elsif (exists $simple_types{$message->{type}}) {
46 3         13 $encoded .= $message->{type} . $message->{data} . "\r\n";
47             }
48              
49             # Invalid type
50             else {
51 0         0 Carp::croak(qq/Unknown message type $message->{type}/);
52             }
53             }
54              
55 11         53 return $encoded;
56             }
57              
58 29     29 1 60 sub get_message { shift @{$_[0]{_messages}} }
  29         197  
59              
60             sub on_message {
61 9     9 1 8356 my ($self, $cb) = @_;
62 9         30 $self->{_on_message_cb} = $cb;
63             }
64              
65             sub parse {
66 40     40 1 3619 my ($self, $input) = @_;
67 40         75 $self->{_buf} .= $input;
68              
69 40         76 my $buf = \$self->{_buf};
70              
71             CHUNK:
72 40         97 while (length $$buf) {
73              
74             # Look for message type and get the actual data,
75             # length of the bulk string or the size of the array
76 63 100       151 if (!$self->{_curr}{type}) {
77 60         112 my $pos = index $$buf, "\r\n";
78 60 100       115 return if $pos < 0; # Wait for more data
79              
80 54         134 $self->{_curr}{type} = substr $$buf, 0, 1;
81 54         112 $self->{_curr}{len} = substr $$buf, 1, $pos - 1;
82 54         99 substr $$buf, 0, $pos + 2, ''; # Remove type + length/data + \r\n
83             }
84              
85             # Order optimized for client decoding;
86             # large array replies usually contain bulk strings
87              
88             # Bulk strings
89 57 100       150 if ($self->{_curr}{type} eq '$') {
    100          
    50          
90 29 100       82 if ($self->{_curr}{len} == -1) {
    100          
91 4         7 $self->{_curr}{data} = undef;
92             }
93             elsif (length($$buf) - 2 < $self->{_curr}{len}) {
94 3         6 return; # Wait for more data
95             }
96             else {
97 22         47 $self->{_curr}{data} = substr $$buf, 0, $self->{_curr}{len}, '';
98 22         38 substr $$buf, 0, 2, ''; # Remove \r\n
99             }
100             }
101              
102             # Simple strings, errors, and integers
103             elsif (exists $simple_types{$self->{_curr}{type}}) {
104 18         41 $self->{_curr}{data} = delete $self->{_curr}{len};
105             }
106              
107             # Arrays
108             elsif ($self->{_curr}{type} eq '*') {
109 10 100       32 $self->{_curr}{data} = $self->{_curr}{len} < 0 ? undef : [];
110              
111             # Fill the array with data
112 10 100       26 if ($self->{_curr}{len} > 0) {
113 8         21 $self->{_curr} = {parent => $self->{_curr}};
114 8         22 next CHUNK;
115             }
116             }
117              
118             # Invalid input
119             else {
120 0         0 Carp::croak(qq/Unexpected input "$self->{_curr}{type}"/);
121             }
122              
123             # Fill parent array with data
124 46         108 while (my $parent = delete $self->{_curr}{parent}) {
125 18         41 delete $self->{_curr}{len};
126 18         27 push @{$parent->{data}}, $self->{_curr};
  18         35  
127              
128 18 100       26 if (@{$parent->{data}} < $parent->{len}) {
  18         48  
129 10         19 $self->{_curr} = {parent => $parent};
130 10         28 next CHUNK;
131             }
132             else {
133 8         24 $self->{_curr} = $parent;
134             }
135             }
136              
137             # Emit a complete message
138 36         58 delete $self->{_curr}{len};
139 36 100       67 if (defined $self->{_on_message_cb}) {
140 8         20 $self->{_on_message_cb}->($self, delete $self->{_curr});
141             } else {
142 28         37 push @{$self->{_messages}}, delete $self->{_curr};
  28         98  
143             }
144             }
145             }
146              
147             1;
148              
149             =head1 NAME
150              
151             Protocol::Redis::Faster - Optimized pure-perl Redis protocol parser/encoder
152              
153             =head1 SYNOPSIS
154              
155             use Protocol::Redis::Faster;
156             my $redis = Protocol::Redis::Faster->new(api => 1) or die "API v1 not supported";
157              
158             $redis->parse("+foo\r\n");
159              
160             # get parsed message
161             my $message = $redis->get_message;
162             print "parsed message: ", $message->{data}, "\n";
163              
164             # asynchronous parsing interface
165             $redis->on_message(sub {
166             my ($redis, $message) = @_;
167             print "parsed message: ", $message->{data}, "\n";
168             });
169              
170             # parse pipelined message
171             $redis->parse("+bar\r\n-error\r\n");
172              
173             # create message
174             print "Get key message:\n",
175             $redis->encode({type => '*', data => [
176             {type => '$', data => 'string'},
177             {type => '+', data => 'OK'}
178             ]});
179              
180             =head1 DESCRIPTION
181              
182             This module implements the L API with more optimized pure-perl
183             internals. See L for usage documentation.
184              
185             This is a low level parsing module, if you are looking to use Redis in Perl,
186             try L, L, or L.
187              
188             =head1 BUGS
189              
190             Report any issues on the public bugtracker.
191              
192             =head1 AUTHORS
193              
194             Dan Book
195              
196             Jan Henning Thorsen
197              
198             =head1 CREDITS
199              
200             Thanks to Sergey Zasenko for the original L
201             and defining the API.
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is Copyright (c) 2019 by Dan Book, Jan Henning Thorsen.
206              
207             This is free software, licensed under:
208              
209             The Artistic License 2.0 (GPL Compatible)
210              
211             =head1 SEE ALSO
212              
213             L