File Coverage

blib/lib/Redis/Namespace.pm
Criterion Covered Total %
statement 67 240 27.9
branch 19 82 23.1
condition 5 41 12.2
subroutine 8 31 25.8
pod 0 11 0.0
total 99 405 24.4


line stmt bran cond sub pod time code
1             package Redis::Namespace;
2              
3 21     21   3318670 use strict;
  21         205  
  21         625  
4 21     21   114 use warnings;
  21         42  
  21         954  
5             our $VERSION = '0.11';
6              
7 21     21   2111 use Redis;
  21         144706  
  21         593  
8 21     21   141 use Carp qw(carp croak);
  21         37  
  21         95853  
9              
10             our %BEFORE_FILTERS = (
11             # do nothing
12             none => sub {
13             my ($self, @args) = @_;
14             return @args;
15             },
16              
17             # GET key => GET namespace:key
18             first => sub {
19             my ($self, @args) = @_;
20             if(@args) {
21             my $first = shift @args;
22             return ($self->add_namespace($first), @args);
23             } else {
24             return @args;
25             }
26             },
27              
28             # MGET key1 key2 => MGET namespace:key1 namespace:key2
29             all => sub {
30             my ($self, @args) = @_;
31             return $self->add_namespace(@args);
32             },
33              
34             exclude_first => sub {
35             my ($self, @args) = @_;
36             if(@args) {
37             my $first = shift @args;
38             return (
39             $first,
40             $self->add_namespace(@args),
41             );
42             } else {
43             return @args;
44             }
45             },
46              
47             # BLPOP key1 key2 timeout => BLPOP namespace:key1 namespace:key2 timeout
48             exclude_last => sub {
49             my ($self, @args) = @_;
50             if(@args) {
51             my $last = pop @args;
52             return (
53             $self->add_namespace(@args),
54             $last
55             );
56             } else {
57             return @args;
58             }
59             },
60              
61             # MSET key1 value1 key2 value2 => MSET namespace:key1 value1 namespace:key2 value2
62             alternate => sub {
63             my ($self, @args) = @_;
64             my @result;
65             for my $i(0..@args-1) {
66             if($i % 2 == 0) {
67             push @result, $self->add_namespace($args[$i]);
68             } else {
69             push @result, $args[$i];
70             }
71             }
72             return @result;
73             },
74              
75             keys => sub {
76             my ($self, $pattern) = @_;
77             return unless defined $pattern;
78             my $namespace = $self->{namespace_escaped};
79             return "$namespace:$pattern";
80             },
81              
82             sort => sub {
83             my ($self, @args) = @_;
84             my @res;
85             if(@args) {
86             my $first = shift @args;
87             push @res, $self->add_namespace($first);
88             }
89             while(@args) {
90             my $option = lc shift @args;
91             if($option eq 'limit') {
92             my $start = shift @args;
93             my $count = shift @args;
94             push @res, $option, $start, $count;
95             } elsif($option eq 'by' || $option eq 'store') {
96             my $key = shift @args;
97             push @res, $option, $self->add_namespace($key);
98             } elsif($option eq 'get') {
99             my $key = shift @args;
100             ($key) = $self->add_namespace($key) unless $key eq '#';
101             push @res, $option, $key;
102             } else {
103             push @res, $option;
104             }
105             }
106             return @res;
107             },
108              
109             # EVAL script 2 key1 key2 arg1 arg2 => EVAL script 2 ns:key1 ns:key2 arg1 arg2
110             eval_style => sub {
111             my ($self, $script, $number, @args) = @_;
112             my @keys = $self->add_namespace(splice @args, 0, $number);
113             return ($script, $number, @keys, @args);
114             },
115              
116             # ZINTERSTORE key0 2 key1 key2 SOME_OPTIONS => ZINTERSTORE ns:key0 2 ns:key1 ns:key2
117             exclude_options => sub {
118             my ($self, $first, $number, @args) = @_;
119             my @keys = $self->add_namespace(splice @args, 0, $number);
120             return ($self->add_namespace($first), $number, @keys, @args);
121             },
122              
123              
124             scan => sub {
125             my ($self, @args) = @_;
126             my @res;
127              
128             my $namespace = $self->{namespace_escaped};
129              
130             # first arg is iteration key
131             if(@args) {
132             my $first = shift @args;
133             push @res, $first;
134             }
135              
136             # parse options
137             my $has_pattern = 0;
138             while(@args) {
139             my $option = lc shift @args;
140             if($option eq 'match') {
141             my $pattern = shift @args;
142             push @res, $option, "$namespace:$pattern";
143             $has_pattern = 1;
144             } elsif($option eq 'count') {
145             my $count = shift @args;
146             push @res, $option, $count;
147             } else {
148             push @res, $option;
149             }
150             }
151              
152             # add pattern option
153             unless($has_pattern) {
154             push @res, 'match', "$namespace:*";
155             }
156              
157             return @res;
158             },
159              
160             # MIGRATE host port key destination-db timeout => MIGRATE host port namespace:key destination-db timeout
161             # MIGRATE host port "" destination-db timeout KEYS => MIGRATE host port namespace:key destination-db timeout
162             migrate => sub {
163             my ($self, @args) = @_;
164             my @res = splice @args, 0, 5;
165              
166             # key may be the empty string in Redis-3.2 and above
167             if(scalar @res >= 3 && $res[2] ne '') {
168             ($res[2]) = $self->add_namespace($res[2]);
169             }
170              
171             while(@args) {
172             my $option = lc shift @args;
173             if($option eq 'keys') {
174             push @res, $option, $self->add_namespace(@args);
175             @args = ();
176             } else {
177             push @res, $option;
178             }
179             }
180             return @res;
181             },
182              
183             # GEORADIUS key longitude latitude radius m|km|ft|mi STORE key STOREDIST key => GEORADIUS namespace:key longitude latitude radius m|km|ft|mi STORE namespace:key STOREDIST namespace:key
184             georadius => sub {
185             my ($self, @args) = @_;
186             my @res;
187              
188             # key
189             if(@args) {
190             my $first = shift @args;
191             push @res, $self->add_namespace($first);
192             }
193              
194             # longitude latitude radius m|km|ft|mi
195             push @res, splice @args, 0, 4;
196              
197             while(@args) {
198             my $option = lc shift @args;
199             if($option eq 'store' || $option eq 'storedist') {
200             my $key = shift @args;
201             push @res, $option, $self->add_namespace($key);
202             } elsif($option eq 'count') {
203             my $count = shift @args;
204             push @res, $option, $count;
205             } else {
206             push @res, $option;
207             }
208             }
209             return @res;
210             },
211              
212             # GEORADIUSBYMEMBER key member radius m|km|ft|mi STORE key STOREDIST key => GEORADIUSBYMEMBER namespace:key member radius m|km|ft|mi STORE namespace:key STOREDIST namespace:key
213             georadiusbymember => sub {
214             my ($self, @args) = @_;
215             my @res;
216              
217             # key
218             if(@args) {
219             my $first = shift @args;
220             push @res, $self->add_namespace($first);
221             }
222              
223             # member radius m|km|ft|mi
224             push @res, splice @args, 0, 3;
225              
226             while(@args) {
227             my $option = lc shift @args;
228             if($option eq 'store' || $option eq 'storedist') {
229             my $key = shift @args;
230             push @res, $option, $self->add_namespace($key);
231             } elsif($option eq 'count') {
232             my $count = shift @args;
233             push @res, $option, $count;
234             } else {
235             push @res, $option;
236             }
237             }
238             return @res;
239             },
240              
241             # XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]
242             # => XREAD [COUNT count] [BLOCK milliseconds] STREAMS namespace:key [namespace:key ...] ID [ID ...]
243             xread => sub {
244             my ($self, @args) = @_;
245             my @res;
246             while(@args) {
247             my $option = lc shift @args;
248             if($option eq 'count' || $option eq 'block') {
249             my $count = shift @args;
250             push @res, $option, $count;
251             } elsif ($option eq 'streams') {
252             my $num = scalar(@args) / 2;
253             push @res, $option, $self->add_namespace(@args[0..$num-1]), @args[$num..2*$num-1];
254             @args = ();
255             } else {
256             push @res, $option;
257             }
258             }
259             return @res;
260             },
261              
262             # XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
263             # => XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS namespace:key [namespace:key ...] ID [ID ...]
264             xreadgroup => sub {
265             my ($self, @args) = @_;
266             my @res;
267              
268             # GROUP group consumer
269             push @res, splice @args, 0, 3;
270              
271             while(@args) {
272             my $option = lc shift @args;
273             if($option eq 'count' || $option eq 'block') {
274             my $count = shift @args;
275             push @res, $option, $count;
276             } elsif ($option eq 'noack') {
277             push @res, $option;
278             } elsif ($option eq 'streams') {
279             my $num = scalar(@args) / 2;
280             push @res, $option, $self->add_namespace(@args[0..$num-1]), @args[$num..2*$num-1];
281             @args = ();
282             } else {
283             push @res, $option;
284             }
285             }
286             return @res;
287             },
288             );
289              
290             our %AFTER_FILTERS = (
291             # do nothing
292             none => sub {
293             my ($self, @args) = @_;
294             return @args;
295             },
296              
297             # namespace:key1 namespace:key2 => key1 key2
298             all => sub {
299             my ($self, @args) = @_;
300             return $self->rem_namespace(@args);
301             },
302              
303             # namespace:key1 value => key1 value
304             first => sub {
305             my ($self, $first, @args) = @_;
306             return ($self->rem_namespace($first), @args);
307             },
308              
309             scan => sub {
310             my ($self, $iter, $list) = @_;
311             my @keys = map { $self->rem_namespace($_) } @$list;
312             return ($iter, \@keys);
313             },
314              
315             # [ [ namespace:key1, [...] ], [ namespace:key2, [...] ] => [ [ key1, [...] ], [ key2, [...] ]
316             xread => sub {
317             my ($self, @args) = @_;
318             return map {
319             if ($_) {
320             my ($key, @rest) = @$_;
321             [$self->rem_namespace($key), @rest];
322             } else {
323             $_;
324             }
325             } @args;
326             },
327             );
328              
329             sub add_namespace {
330 20     20 0 72 my ($self, @args) = @_;
331 20         35 my $namespace = $self->{namespace};
332 20 50       42 return @args unless $namespace;
333              
334 20         28 my @result;
335 20         39 for my $item(@args) {
336 32         44 my $type = ref $item;
337 32 100 66     127 if($item && !$type) {
    100          
    100          
    100          
338 16         71 push @result, "$namespace:$item";
339             } elsif($type eq 'SCALAR') {
340 4         13 push @result, \"$namespace:$$item";
341             } elsif($type eq 'ARRAY') {
342 4         24 push @result, [$self->add_namespace(@$item)];
343             } elsif($type eq 'HASH') {
344 4         8 my %hash;
345 4         17 while (my ($key, $value) = each %$item) {
346 6         24 my ($new_key) = $self->add_namespace($key);
347 6         26 $hash{$new_key} = $value;
348             }
349 4         10 push @result, \%hash;
350             } else {
351 4         8 push @result, $item;
352             }
353             }
354 20         95 return @result;
355             }
356              
357             sub rem_namespace {
358 20     20 0 78 my ($self, @args) = @_;
359 20         33 my $namespace = $self->{namespace};
360 20 50       37 return @args unless $namespace;
361              
362 20         26 my @result;
363 20         36 for my $item(@args) {
364 32         53 my $type = ref $item;
365 32 100 66     122 if($item && !$type) {
    100          
    100          
    100          
366 16         93 $item =~ s/^\Q$namespace://;
367 16         47 push @result, $item;
368             } elsif($type eq 'SCALAR') {
369 4         9 my $tmp = $$item;
370 4         37 $tmp =~ s/^\Q$namespace://;
371 4         12 push @result, \$tmp;
372             } elsif($type eq 'ARRAY') {
373 4         22 push @result, [$self->rem_namespace(@$item)];
374             } elsif($type eq 'HASH') {
375 4         6 my %hash;
376 4         18 while (my ($key, $value) = each %$item) {
377 6         14 my ($new_key) = $self->rem_namespace($key);
378 6         32 $hash{$new_key} = $value;
379             }
380 4         11 push @result, \%hash;
381             } else {
382 4         8 push @result, $item;
383             }
384             }
385 20         105 return @result;
386             }
387              
388             # %UNSAFE_COMMANDS may break other namepace and/or change the state of redis-server.
389             # these commands are disable in strict mode.
390             our %UNSAFE_COMMANDS = (
391             cluster => 1,
392             config => 1,
393             flushall => 1,
394             flushdb => 1,
395             readonly => 1,
396             readwrite => 1,
397             replicaof => 1,
398             slaveof => 1,
399             shutdown => 1,
400             );
401              
402             our %COMMANDS = (
403             append => [ 'first' ],
404             auth => [],
405             bgrewriteaof => [],
406             bgsave => [],
407             bitcount => [ 'first' ],
408             bitfield => [ 'first' ],
409             bitpos => [ 'first' ],
410             bitop => [ 'exclude_first' ],
411             blpop => [ 'exclude_last', 'first' ],
412             brpop => [ 'exclude_last', 'first' ],
413             brpoplpush => [ 'exclude_last' ],
414             bzpopmax => [ 'exclude_last', 'first' ],
415             bzpopmin => [ 'exclude_last', 'first' ],
416             client => [],
417             cluster => [],
418             command => [],
419             config => [],
420             dbsize => [],
421             debug => [ 'exclude_first' ],
422             decr => [ 'first' ],
423             decrby => [ 'first' ],
424             del => [ 'all' ],
425             discard => [],
426             dump => [ 'first' ],
427             echo => [],
428             exists => [ 'first' ],
429             expire => [ 'first' ],
430             expireat => [ 'first' ],
431             eval => [ 'eval_style' ],
432             evalsha => [ 'eval_style' ],
433             exec => [],
434             flushall => [],
435             flushdb => [],
436             geoadd => [ 'first' ],
437             geodist => [ 'first' ],
438             geohash => [ 'first' ],
439             geopos => [ 'first' ],
440             georadius => [ 'georadius' ],
441             georadiusbymember=> [ 'georadiusbymember' ],
442             get => [ 'first' ],
443             getbit => [ 'first' ],
444             getrange => [ 'first' ],
445             getset => [ 'first' ],
446             hscan => [ 'first' ],
447             hset => [ 'first' ],
448             hsetnx => [ 'first' ],
449             hstrlen => [ 'first' ],
450             hget => [ 'first' ],
451             hincrby => [ 'first' ],
452             hincrbyfloat => [ 'first' ],
453             hmget => [ 'first' ],
454             hmset => [ 'first' ],
455             hdel => [ 'first' ],
456             hexists => [ 'first' ],
457             hlen => [ 'first' ],
458             hkeys => [ 'first' ],
459             hvals => [ 'first' ],
460             hgetall => [ 'first' ],
461             incr => [ 'first' ],
462             incrby => [ 'first' ],
463             incrbyfloat => [ 'first' ],
464             info => [],
465             keys => [ 'keys', 'all' ],
466             lastsave => [],
467             lindex => [ 'first' ],
468             linsert => [ 'first' ],
469             llen => [ 'first' ],
470             lpop => [ 'first' ],
471             lpush => [ 'first' ],
472             lpushx => [ 'first' ],
473             lrange => [ 'first' ],
474             lrem => [ 'first' ],
475             lset => [ 'first' ],
476             ltrim => [ 'first' ],
477             memory => [],
478             mget => [ 'all' ],
479             migrate => [ 'migrate' ],
480             monitor => [],
481             move => [ 'first' ],
482             mscan => [ 'first' ],
483             mset => [ 'alternate' ],
484             msetnx => [ 'alternate' ],
485             object => [ 'exclude_first' ],
486             persist => [ 'first' ],
487             pexpire => [ 'first' ],
488             pexpireat => [ 'first' ],
489             pfadd => [ 'first' ],
490             pfcount => [ 'all' ],
491             pfmerge => [ 'all' ],
492             ping => [],
493             psetex => [ 'first' ],
494             psubscribe => [ 'all' ],
495             pttl => [ 'first' ],
496             publish => [ 'first' ],
497             punsubscribe => [ 'all' ],
498             quit => [],
499             randomkey => [],
500             readonly => [],
501             readwrite => [],
502             rename => [ 'all' ],
503             renamenx => [ 'all' ],
504             replicaof => [],
505             restore => [ 'first' ],
506             role => [],
507             rpop => [ 'first' ],
508             rpoplpush => [ 'all' ],
509             rpush => [ 'first' ],
510             rpushx => [ 'first' ],
511             sadd => [ 'first' ],
512             save => [],
513             scard => [ 'first' ],
514             script => [],
515             sdiff => [ 'all' ],
516             sdiffstore => [ 'all' ],
517             select => [],
518             set => [ 'first' ],
519             setbit => [ 'first' ],
520             setex => [ 'first' ],
521             setnx => [ 'first' ],
522             setrange => [ 'first' ],
523             shutdown => [],
524             sinter => [ 'all' ],
525             sinterstore => [ 'all' ],
526             sismember => [ 'first' ],
527             slaveof => [],
528             slowlog => [],
529             smembers => [ 'first' ],
530             smove => [ 'exclude_last' ],
531             scan => [ 'scan', 'scan' ],
532             sort => [ 'sort' ],
533             spop => [ 'first' ],
534             srandmember => [ 'first' ],
535             srem => [ 'first' ],
536             sscan => [ 'first' ],
537             strlen => [ 'first' ],
538             subscribe => [ 'all' ],
539             sunion => [ 'all' ],
540             sunionstore => [ 'all' ],
541             swapdb => [],
542             sync => [],
543             time => [],
544             touch => [ 'all' ],
545             ttl => [ 'first' ],
546             type => [ 'first' ],
547             unsubscribe => [ 'all' ],
548             unlink => [ 'all' ],
549             unwatch => [],
550             wait => [],
551             watch => [ 'all' ],
552             xack => [ 'first' ],
553             xadd => [ 'first' ],
554             xclaim => [ 'first' ],
555             xdel => [ 'first' ],
556             xgroup => {
557             create => [ 'first' ],
558             setid => [ 'first' ],
559             destroy => [ 'first' ],
560             delconsumer => [ 'first' ],
561             help => [],
562             },
563             xinfo => {
564             consumers => [ 'first' ],
565             groups => [ 'first' ],
566             stream => [ 'first' ],
567             help => [],
568             },
569             xlen => [ 'all' ],
570             xpending => [ 'first' ],
571             xrange => [ 'first' ],
572             xread => [ 'xread', 'xread' ],
573             xreadgroup => [ 'xreadgroup', 'xread' ],
574             xrevrange => [ 'first' ],
575             xtrim => [ 'first' ],
576             zadd => [ 'first' ],
577             zcard => [ 'first' ],
578             zcount => [ 'first' ],
579             zincrby => [ 'first' ],
580             zinterstore => [ 'exclude_options' ],
581             zlexcount => [ 'first' ],
582             zpopmax => [ 'first' ],
583             zpopmin => [ 'first' ],
584             zrange => [ 'first' ],
585             zrangebylex => [ 'first' ],
586             zrangebyscore => [ 'first' ],
587             zrank => [ 'first' ],
588             zrem => [ 'first' ],
589             zremrangebylex => [ 'first' ],
590             zremrangebyrank => [ 'first' ],
591             zremrangebyscore => [ 'first' ],
592             zrevrange => [ 'first' ],
593             zrevrangebylex => [ 'first' ],
594             zrevrangebyscore => [ 'first' ],
595             zrevrank => [ 'first' ],
596             zscan => [ 'first' ],
597             zscore => [ 'first' ],
598             zunionstore => [ 'exclude_options' ],
599              
600             multi => [],
601             );
602              
603             sub new {
604 4     4 0 8838 my $class = shift;
605 4         16 my %args = @_;
606 4         10 my $self = bless {}, $class;
607              
608 4   33     22 $self->{redis} = $args{redis} || Redis->new(%args);
609 4         9 $self->{namespace} = $args{namespace};
610 4         9 $self->{warning} = $args{warning};
611 4         9 $self->{strict} = $args{strict};
612 4         8 $self->{subscribers} = {};
613 4 50       24 if ($args{guess}) {
614 0         0 my $count = eval { $self->{redis}->command_count };
  0         0  
615 0 0       0 if ($count) {
    0          
616 0         0 $self->{guess} = 1;
617             } elsif ($self->{warning}) {
618 0         0 my $version = $self->{redis}->info->{redis_version};
619 0         0 carp "guess option requires 2.8.13 or later. your redis version is $version";
620             }
621             }
622 4         10 $self->{guess_cache} = {};
623 4         9 $self->{movablekeys} = {};
624              
625             # escape for pattern
626 4         7 my $escaped = $args{namespace};
627 4         14 $escaped =~ s/([[?*\\])/"\\$1"/ge;
  0         0  
628 4         13 $self->{namespace_escaped} = $escaped;
629              
630 4         12 return $self;
631             }
632              
633             sub _wrap_method {
634 0     0     my ($class, $command) = @_;
635 0           my ($cmd, @extra) = split /_/, lc($command);
636 0           my $filters = $COMMANDS{$cmd};
637 0           my ($before, $after);
638 0           my @subcommand = ();
639              
640 0 0         if ($filters) {
641 0 0         if (ref $filters eq 'HASH') {
642             # the target command has sub-commands
643 0 0         if (@extra > 0) {
644 0           my $subcommand = shift @extra;
645 0           @subcommand = ($subcommand);
646 0   0       $before = $BEFORE_FILTERS{$filters->{$subcommand}[0] // 'none'};
647 0   0       $after = $AFTER_FILTERS{$filters->{$subcommand}[1] // 'none'};
648             } else {
649             $before = sub {
650 0     0     my ($self, $subcommand, @arg) = @_;
651 0   0       my $before = $BEFORE_FILTERS{$filters->{$subcommand}[0] // 'none'};
652 0   0       $after = $AFTER_FILTERS{$filters->{$subcommand}[1] // 'none'};
653 0           return ($subcommand, $before->($self, @arg));
654 0           };
655 0           $after = $AFTER_FILTERS{'none'};
656             }
657             } else {
658 0   0       $before = $BEFORE_FILTERS{$filters->[0] // 'none'};
659 0   0       $after = $AFTER_FILTERS{$filters->[1] // 'none'};
660             }
661             }
662              
663             return sub {
664 0     0     my ($self, @args) = @_;
665 0           my $redis = $self->{redis};
666 0           my $wantarray = wantarray;
667 0           my ($before, $after) = ($before, $after);
668              
669 0 0 0       if ($self->{strict} && $UNSAFE_COMMANDS{$command}) {
670 0           croak "unsafe command '$command'";
671             }
672              
673 0 0 0       if (!$before || !$after) {
674 0 0         if ($self->{strict}) {
675 0           croak "unknown command '$command'";
676             }
677 0           ($before, $after) = $self->_guess($command, @subcommand, @extra, @args);
678             }
679              
680 0 0 0       if(@args && ref $args[-1] eq 'CODE') {
681 0           my $cb = pop @args;
682 0           @args = (@subcommand, $before->($self, @extra, @args));
683             push @args, sub {
684 0           my ($result, $error) = @_;
685 0           $cb->($after->($self, $result), $error);
686 0           };
687             } else {
688 0           @args = (@subcommand, $before->($self, @extra, @args));
689             }
690              
691 0 0         if(!$wantarray) {
    0          
692 0           $redis->$cmd(@args);
693             } elsif($wantarray) {
694 0           my @result = $redis->$cmd(@args);
695 0           return $after->($self, @result);
696             } else {
697 0           my $result = $redis->$cmd(@args);
698 0           return $after->($self, $result);
699             }
700 0           };
701             }
702              
703             sub _guess {
704 0     0     my ($self, $command, @args) = @_;
705 0 0         if (!$self->{guess}) {
706 0           carp "unknown command '$command'. passing arguments to the redis server as is.";
707 0           return $BEFORE_FILTERS{none}, $AFTER_FILTERS{none};
708             }
709              
710 0 0         if (my $cache = $self->{guess_cache}{$command}) {
711 0           return @$cache;
712             }
713              
714 0           my $movablekeys = $self->{movablekeys}{$command};
715 0 0         if ($movablekeys) {
716 0           return $self->_guess_movablekeys($command, @args);
717             }
718              
719 0           my $info = $self->{redis}->command_info($command);
720 0 0         my ($name, $num, $flags, $first, $last, $step) = @{$info->[0] || []};
  0            
721              
722 0 0         unless ($name) {
723 0 0         if ($self->{warning}) {
724 0           carp "unknown command '$command'. passing arguments to the redis server as is.";
725             }
726 0           my ($before, $after) = ($BEFORE_FILTERS{none}, $AFTER_FILTERS{none});
727 0           $self->{guess_cache}{$command} = [$before, $after];
728 0           return $before, $after;
729             }
730              
731 0 0         ($movablekeys) = grep { $_ eq 'movablekeys' } @{$flags || []};
  0            
  0            
732 0 0         if ($movablekeys) {
733 0           $self->{movablekeys}{$command} = 1;
734 0           return $self->_guess_movablekeys($command, @args);
735             }
736              
737             my $before = sub {
738 0     0     my ($self, @args) = @_;
739 0 0         if ($first > 0) {
740 0   0       for (my $i = $first; $i <= @args && ($last < 0 || $i <= $last); $i += $step) {
      0        
741 0           ($args[$i-1]) = $self->add_namespace($args[$i-1]);
742             }
743             }
744 0           return @args;
745 0           };
746 0           my $after = $AFTER_FILTERS{none};
747 0           $self->{guess_cache}{$command} = [$before, $after];
748 0           return $before, $after;
749             }
750              
751             sub _guess_movablekeys {
752 0     0     my ($self, $command, @args) = @_;
753 0 0 0       if(@args && ref $args[-1] eq 'CODE') {
754 0           pop @args; # ignore callback function
755             }
756              
757 0           my @keys = eval { $self->{redis}->command_getkeys($command, @args) }
758 0 0         or return $BEFORE_FILTERS{none}, $AFTER_FILTERS{none};
759 0           my @positions = ();
760 0           my @list = ();
761              
762             # search the positions of keys.
763 0           my $search; $search = sub {
764 0     0     my ($i, $start) = @_;
765 0           my $key = $keys[$i];
766 0           for (my $j = $start; $j < @args; $j++) {
767 0 0         next if $args[$j] ne $key;
768 0           push @positions, $j;
769 0 0         if ($i+1 < @keys) {
770 0           $search->($i+1, $j+1);
771             } else {
772 0           push @list, [@positions];
773             }
774 0           pop @positions;
775             }
776 0           };
777 0           $search->(0, 0);
778              
779 0 0         if (@list == 0) {
    0          
780 0           croak "fail to guess key positions of command '$command'";
781             } elsif (@list == 1) {
782             # found keys
783 0           my $positions = $list[0];
784             return sub {
785 0     0     my ($self, @args) = @_;
786 0           @args[@$positions] = $self->add_namespace(@args[@$positions]);
787 0           return @args;
788             }, $AFTER_FILTERS{none}
789 0           }
790              
791             # found keys, but their positions are ambiguous
792 0           my $prefix = "test-key-$^T-$$-";
793 0           my @want = map { "$prefix$_" } @keys;
  0            
794             LOOP:
795 0           for my $positions(@list) {
796 0           my @args = @args;
797 0           for my $i(@$positions) {
798 0           $args[$i] = $prefix . $args[$i];
799             }
800 0           my @keys = eval { $self->{redis}->command_getkeys($command, @args) };
  0            
801              
802 0 0         if (scalar(@keys) != scalar(@want)) {
803 0           next;
804             }
805 0           for my $i(0..scalar(@keys)-1) {
806 0 0         if ($keys[$i] ne $want[$i]) {
807             next LOOP
808 0           }
809             }
810              
811             # found!
812             return sub {
813 0     0     my ($self, @args) = @_;
814 0           @args[@$positions] = $self->add_namespace(@args[@$positions]);
815 0           return @args;
816             }, $AFTER_FILTERS{none}
817 0           }
818              
819 0           croak "fail to guess key positions of command '$command'";
820             }
821              
822       0     sub DESTROY { }
823              
824             our $AUTOLOAD;
825             sub AUTOLOAD {
826 0     0     my $command = $AUTOLOAD;
827 0           $command =~ s/.*://;
828              
829 0           my $method = Redis::Namespace->_wrap_method($command);
830              
831             # Save this method for future calls
832 21     21   186 no strict 'refs';
  21         49  
  21         14384  
833 0           *$AUTOLOAD = $method;
834              
835 0           goto $method;
836             }
837              
838             # special commands. they are not redis commands.
839             sub wait_one_response {
840 0     0 0   my $self = shift;
841 0           return $self->{redis}->wait_one_response(@_);
842             }
843             sub wait_all_responses {
844 0     0 0   my $self = shift;
845 0           return $self->{redis}->wait_all_responses(@_);
846             }
847              
848             sub __wrap_subcb {
849 0     0     my ($self, $cb) = @_;
850 0           my $subscribers = $self->{subscribers};
851             my $callback = $subscribers->{$cb} // sub {
852 0     0     my ($message, $topic, $subscribed_topic) = @_;
853 0           $cb->($message, $self->rem_namespace($topic), $self->rem_namespace($subscribed_topic));
854 0   0       };
855 0           $subscribers->{$cb} = $callback;
856 0           return $callback;
857             }
858              
859             sub __subscribe {
860 0     0     my ($self, $command, @args) = @_;
861 0           my $cb = pop @args;
862 0 0         confess("missing required callback in call to $command(), ")
863             unless ref($cb) eq 'CODE';
864              
865 0           my $redis = $self->{redis};
866 0           my $callback = $self->__wrap_subcb($cb);
867 0           @args = $BEFORE_FILTERS{all}->($self, @args);
868 0           return $redis->$command(@args, $callback);
869             }
870              
871             sub __psubscribe {
872 0     0     my ($self, $command, @args) = @_;
873 0           my $cb = pop @args;
874 0 0         confess("missing required callback in call to $command(), ")
875             unless ref($cb) eq 'CODE';
876              
877 0           my $redis = $self->{redis};
878 0           my $callback = $self->__wrap_subcb($cb);
879 0           my $namespace = $self->{namespace_escaped};
880 0           @args = map { "$namespace:$_" } @args;
  0            
881 0           return $redis->$command(@args, $callback);
882             }
883              
884             # PubSub commands
885             sub wait_for_messages {
886 0     0 0   my $self = shift;
887 0           return $self->{redis}->wait_for_messages(@_);
888             }
889              
890             sub is_subscriber {
891 0     0 0   my $self = shift;
892 0           return $self->{redis}->is_subscriber(@_);
893             }
894              
895             sub subscribe {
896 0     0 0   my $self = shift;
897 0           return $self->__subscribe('subscribe', @_);
898             }
899              
900             sub psubscribe {
901 0     0 0   my $self = shift;
902 0           return $self->__psubscribe('psubscribe', @_);
903             }
904              
905             sub unsubscribe {
906 0     0 0   my $self = shift;
907 0           return $self->__subscribe('unsubscribe', @_);
908             }
909              
910             sub punsubscribe {
911 0     0 0   my $self = shift;
912 0           return $self->__psubscribe('punsubscribe', @_);
913             }
914              
915             1;
916             __END__