File Coverage

lib/Redis/SQLite.pm
Criterion Covered Total %
statement 17 213 7.9
branch 1 56 1.7
condition 2 30 6.6
subroutine 4 30 13.3
pod 26 26 100.0
total 50 355 14.0


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Redis::SQLite - Redis-Compatible module which writes to SQLite.
5              
6             =cut
7              
8             =head1 SYNOPSIS
9              
10             =for example begin
11              
12             #!/usr/bin/perl -w
13              
14             use Redis::SQLite;
15             use strict;
16              
17             my $db = Redis::SQLite->new();
18              
19             $db->set( "foo", "bar" );
20              
21             print $db->get( "foo" ) . "\n";
22              
23             =for example end
24              
25              
26             =head1 DESCRIPTION
27              
28             This package is an implementation of the L Perl-client API, which
29             stores all data in an SQLite database rather than in RAM.
30              
31             It is B a drop-in replacement, because it doesn't implement all the
32             features you'd expect from the real Redis module. Just enough to be useful.
33              
34             =cut
35              
36             =head1 COMPATIBILITY
37              
38             This module is designed to be source compatible with the L module,
39             providing you're only operating upon either sets or simple strings.
40             Specifically we do not support ZSET or HASH-related operations.
41              
42             The following methods are implemented as part of the basic-functionality:
43              
44             =over 8
45              
46             =item APPEND
47              
48             =item EXISTS
49              
50             =item GET
51              
52             =item GETSET
53              
54             =item SET
55              
56             =item TYPE
57              
58             =item INCR
59              
60             =item INCRBY
61              
62             =item DECR
63              
64             =item DECRBY
65              
66             =item DEL
67              
68             =item STRLEN
69              
70             =back
71              
72             The following set-related methods are implemented:
73              
74             =over 8
75              
76             =item SADD
77              
78             =item SCARD
79              
80             =item SDIFF
81              
82             =item SDIFFSTORE
83              
84             =item SINTER
85              
86             =item SINTERSTORE
87              
88             =item SISMEMBER
89              
90             =item SMEMBERS
91              
92             =item SMOVE
93              
94             =item SPOP
95              
96             =item SRANDMEMBER
97              
98             =item SREM
99              
100             =item SUNION
101              
102             =item SUNIONSTORE
103              
104             =back
105              
106             The only missing set-method is C, other methods which are missing
107             will raise a Cing.
108              
109             =cut
110              
111             =head1 METHODS
112              
113             =cut
114              
115              
116             package Redis::SQLite;
117              
118              
119              
120 14     14   581568 use strict;
  14         47  
  14         444  
121 14     14   57 use warnings;
  14         18  
  14         433  
122 14     14   25309 use DBI;
  14         229465  
  14         31817  
123              
124              
125             our $VERSION = '0.1';
126              
127              
128             =head2 new
129              
130             Constructor. The only (optional) argument is C which will
131             change the default SQLite database-file location, if unspecified
132             C<~/.predis.db> will be used.
133              
134             =cut
135              
136             sub new
137             {
138 13     13 1 17790 my ( $proto, %supplied ) = (@_);
139 13   33     145 my $class = ref($proto) || $proto;
140              
141 13         29 my $self = {};
142 13         33 bless( $self, $class );
143              
144 13   33     74 my $file = $supplied{ 'path' } || $ENV{ 'HOME' } . "/.predis.db";
145 13         38 my $create = 1;
146 13 50       415 $create = 0 if ( -e $file );
147              
148 13         211 $self->{ 'db' } =
149             DBI->connect( "dbi:SQLite:dbname=$file", "", "", { AutoCommit => 1 } );
150              
151             #
152             # Populate the database tables, if it was missing.
153             #
154 0 0         if ($create)
155             {
156 0           $self->{ 'db' }->do(
157             "CREATE TABLE string (id INTEGER PRIMARY KEY, key UNIQUE, val );");
158 0           $self->{ 'db' }
159             ->do("CREATE TABLE sets (id INTEGER PRIMARY KEY, key, val );");
160             }
161              
162             #
163             # This is potentially risky, but improves the throughput by several
164             # orders of magnitude.
165             #
166 0 0         if ( !$ENV{ 'SAFE' } )
167             {
168 0           $self->{ 'db' }->do("PRAGMA synchronous = OFF");
169 0           $self->{ 'db' }->do("PRAGMA journal_mode = MEMORY");
170             }
171              
172 0           return $self;
173             }
174              
175              
176             =head2 append
177              
178             Append the given string to the contents of the existing key, creating it
179             if didn't previously exist.
180              
181             =cut
182              
183             sub append
184             {
185 0     0 1   my ( $self, $key, $data ) = (@_);
186              
187 0           my $r = $self->get($key);
188 0           $r .= $data;
189 0           $self->set( $key, $r );
190             }
191              
192              
193             =head2 exists
194              
195             Does the given key exist?
196              
197             =cut
198              
199             sub exists
200             {
201 0     0 1   my ( $self, $key ) = (@_);
202              
203 0           my $sql = $self->{ 'db' }->prepare("SELECT key FROM string WHERE key=?");
204 0           $sql->execute($key);
205 0   0       my $x = $sql->fetchrow_array() || undef;
206 0           $sql->finish();
207              
208 0 0         if ($x)
209             {
210 0           return 1;
211             }
212              
213 0           $sql = $self->{ 'db' }->prepare("SELECT key FROM sets WHERE key=?");
214 0           $sql->execute($key);
215 0   0       $x = $sql->fetchrow_array() || undef;
216 0           $sql->finish();
217              
218 0 0         if ($x)
219             {
220 0           return 1;
221             }
222              
223 0           return 0;
224             }
225              
226              
227             =head2 get
228              
229             Get the value of a string-key. Returns C if the key didn't exist,
230             or contain data.
231              
232             =cut
233              
234             sub get
235             {
236 0     0 1   my ( $self, $key ) = (@_);
237              
238 0 0         if ( !$self->{ 'get' } )
239             {
240             $self->{ 'get' } =
241 0           $self->{ 'db' }->prepare("SELECT val FROM string WHERE key=?");
242             }
243 0           $self->{ 'get' }->execute($key);
244 0           my $x = $self->{ 'get' }->fetchrow_array();
245 0           $self->{ 'get' }->finish();
246 0           return ($x);
247             }
248              
249              
250              
251             =head2 getset
252              
253             Update the value of a key, and return the previous value if any.
254              
255             =cut
256              
257             sub getset
258             {
259 0     0 1   my ( $self, $key, $val ) = (@_);
260              
261 0           my $old = $self->get($key);
262 0           $self->set( $key, $val );
263              
264 0           return ($old);
265             }
266              
267              
268             =head2 strlen
269              
270             Return the length of the given value of the given key.
271              
272             =cut
273              
274             sub strlen
275             {
276 0     0 1   my ( $self, $key ) = (@_);
277              
278 0           my $data = $self->get($key);
279 0 0         if ( defined($data) )
280             {
281 0           return ( length($data) );
282             }
283 0           return 0;
284             }
285              
286              
287             =head2 set
288              
289             Set the value of a string-key.
290              
291             =cut
292              
293             sub set
294             {
295 0     0 1   my ( $self, $key, $val ) = (@_);
296              
297 0 0         if ( !$self->{ 'ins' } )
298             {
299             $self->{ 'ins' } =
300 0           $self->{ 'db' }
301             ->prepare("INSERT OR REPLACE INTO string (key,val) VALUES( ?,? )");
302             }
303 0           $self->{ 'ins' }->execute( $key, $val );
304 0           $self->{ 'ins' }->finish();
305              
306             }
307              
308              
309             =head2 type
310              
311             Return the type of the named key.
312              
313             =cut
314              
315             sub type
316             {
317 0     0 1   my ( $self, $key ) = (@_);
318              
319              
320 0           my $sql = $self->{ 'db' }->prepare("SELECT key FROM string WHERE key=?");
321 0           $sql->execute($key);
322 0   0       my $x = $sql->fetchrow_array() || undef;
323 0           $sql->finish();
324              
325 0 0         return 'string' if ($x);
326              
327 0           $sql = $self->{ 'db' }->prepare("SELECT key FROM sets WHERE key=?");
328 0           $sql->execute($key);
329 0   0       $x = $sql->fetchrow_array() || undef;
330 0           $sql->finish();
331              
332 0 0         return 'set' if ($x);
333              
334 0           return undef;
335             }
336              
337              
338             =head2 incr
339              
340             Increment and return the value of an (integer) string-key.
341              
342             =cut
343              
344             sub incr
345             {
346 0     0 1   my ( $self, $key ) = (@_);
347              
348 0           return ( $self->incrby( $key, 1 ) );
349             }
350              
351              
352              
353             =head2 incrby
354              
355             Increment and return the value of an (integer) string-key.
356              
357             =cut
358              
359             sub incrby
360             {
361 0     0 1   my ( $self, $key, $amt ) = (@_);
362              
363 0 0         $amt = 1 if ( !defined($amt) );
364              
365 0   0       my $cur = $self->get($key) || 0;
366 0           $cur += $amt;
367 0           $self->set( $key, $cur );
368              
369 0           return ($cur);
370             }
371              
372              
373             =head2 decr
374              
375             Decrement and return the value of an (integer) string-key.
376              
377             =cut
378              
379             sub decr
380             {
381 0     0 1   my ( $self, $key ) = (@_);
382              
383 0           return ( $self->decrby( $key, 1 ) );
384             }
385              
386              
387              
388             =head2 decrby
389              
390             Decrement and return the value of an (integer) string-key.
391              
392             =cut
393              
394             sub decrby
395             {
396 0     0 1   my ( $self, $key, $amt ) = (@_);
397              
398 0 0         $amt = 1 if ( !defined($amt) );
399              
400 0   0       my $cur = $self->get($key) || 0;
401 0           $cur -= $amt;
402 0           $self->set( $key, $cur );
403              
404 0           return ($cur);
405             }
406              
407              
408             =head2 del
409              
410             Delete a given key, regardless of whether it holds a string or a set.
411              
412             =cut
413              
414             sub del
415             {
416 0     0 1   my ( $self, $key ) = (@_);
417              
418             # strings
419 0           my $str = $self->{ 'db' }->prepare("DELETE FROM string WHERE key=?");
420 0           $str->execute($key);
421 0           $str->finish();
422              
423             # Deleted a string-keyu
424 0 0         return 1 if ( $str->rows > 0 );
425              
426             # sets
427 0           my $set = $self->{ 'db' }->prepare("DELETE FROM sets WHERE key=?");
428 0           $set->execute($key);
429 0           $set->finish();
430              
431             # Deleted a set-key.
432 0 0         return 1 if ( $set->rows > 0 );
433              
434             # Deleted nothing.
435 0           return 0;
436             }
437              
438              
439             =head2 keys
440              
441             Return the names of each known key.
442              
443             These can be optionally filtered by a (perl) regular expression, for example:
444              
445             =for example begin
446              
447             $redis->set( "foo", 1 );
448             $redis->set( "moo", 1 );
449              
450             $redis->keys( "^f" ); # -> [ "foo" ]
451             $redis->keys( "oo\$" ); # -> [ "foo", "moo" ]
452              
453             =for example end
454              
455             =cut
456              
457             sub keys
458             {
459 0     0 1   my ( $self, $pattern ) = (@_);
460              
461             # Get all keys into this hash
462 0           my %known;
463              
464             # We run the same query against two tables.
465 0           foreach my $table (qw! string sets !)
466             {
467             # Get the names of the key.
468 0           my $str = $self->{ 'db' }->prepare("SELECT key FROM $table");
469 0           $str->execute();
470 0           while ( my ($name) = $str->fetchrow_array )
471             {
472 0           $known{ $name } += 1;
473             }
474 0           $str->finish();
475             }
476              
477             # The keys we've found
478 0           my @keys = keys %known;
479              
480 0 0         if ($pattern)
481             {
482 0           my @ret;
483 0           foreach my $ent (@keys)
484             {
485 0 0         push( @ret, $ent ) if ( $ent =~ /$pattern/ );
486             }
487 0           return (@ret);
488             }
489             else
490             {
491 0           return (@keys);
492             }
493             }
494              
495              
496             =head2 smembers
497              
498             Return the members of the given set.
499              
500             =cut
501              
502             sub smembers
503             {
504 0     0 1   my ( $self, $key ) = (@_);
505              
506 0 0         if ( !$self->{ 'smembers' } )
507             {
508             $self->{ 'smembers' } =
509 0           $self->{ 'db' }->prepare("SELECT val FROM sets WHERE key=?");
510             }
511 0           $self->{ 'smembers' }->execute($key);
512              
513 0           my @vals;
514 0           while ( my ($name) = $self->{ 'smembers' }->fetchrow_array )
515             {
516 0           push( @vals, $name );
517             }
518 0           $self->{ 'smembers' }->finish();
519              
520 0           return (@vals);
521             }
522              
523              
524             =head2 smove
525              
526             Move a member from a given set to a new one.
527              
528             =cut
529              
530             sub smove
531             {
532 0     0 1   my ( $self, $src, $dst, $ent ) = (@_);
533              
534             # Get the value from the original set
535 0           my $sql = $self->{ 'db' }
536             ->prepare("UPDATE sets SET key=? WHERE ( key=? AND val=?)");
537              
538 0           $sql->execute( $dst, $src, $ent );
539 0           $sql->finish();
540              
541 0 0         if ( $sql->rows > 0 )
542             {
543 0           return 1;
544             }
545 0           return 0;
546              
547             }
548              
549              
550             =head2 sismember
551              
552             Is the given item a member of the set?
553              
554             =cut
555              
556             sub sismember
557             {
558 0     0 1   my ( $self, $set, $key ) = (@_);
559              
560             my $sql =
561 0           $self->{ 'db' }->prepare("SELECT val FROM sets WHERE key=? AND val=?");
562 0           $sql->execute( $set, $key );
563              
564 0   0       my $x = $sql->fetchrow_array() || undef;
565 0           $sql->finish();
566              
567 0 0 0       if ( defined($x) && ( $x eq $key ) )
568             {
569 0           return 1;
570             }
571 0           return 0;
572             }
573              
574              
575             =head2 sadd
576              
577             Add a member to a set.
578              
579             =cut
580              
581             sub sadd
582             {
583 0     0 1   my ( $self, $key, $val ) = (@_);
584              
585 0 0         if ( !$self->{ 'sadd' } )
586             {
587             $self->{ 'sadd' } =
588 0           $self->{ 'db' }->prepare(
589             "INSERT INTO sets (key,val) SELECT ?,? WHERE NOT EXISTS( SELECT key, val FROM sets WHERE key=? AND val=? );"
590             );
591              
592             }
593 0           $self->{ 'sadd' }->execute( $key, $val, $key, $val );
594 0           $self->{ 'sadd' }->finish();
595              
596 0 0         if ( $self->{ 'sadd' }->rows > 0 )
597             {
598 0           return 1;
599             }
600 0           return 0;
601              
602             }
603              
604              
605             =head2 srem
606              
607             Remove a member from a set.
608              
609             =cut
610              
611             sub srem
612             {
613 0     0 1   my ( $self, $key, $val ) = (@_);
614              
615 0 0         if ( !$self->{ 'srem' } )
616             {
617             $self->{ 'srem' } =
618 0           $self->{ 'db' }->prepare("DELETE FROM sets WHERE (key=? AND val=?)");
619             }
620 0           $self->{ 'srem' }->execute( $key, $val );
621 0           $self->{ 'srem' }->finish();
622              
623 0 0         if ( $self->{ 'srem' }->rows > 0 )
624             {
625 0           return 1;
626             }
627 0           return 0;
628             }
629              
630              
631             =head2 spop
632              
633             Remove a given number of elements from the named set, and return them.
634              
635             =cut
636              
637             sub spop
638             {
639 0     0 1   my ( $self, $key, $count ) = (@_);
640              
641 0 0         $count = 1 if ( !defined($count) );
642              
643 0           my @res;
644              
645 0   0       while ( ( $count > 0 ) && ( $count <= $self->scard($key) ) )
646             {
647 0           my $rand = $self->srandmember($key);
648 0           push( @res, $rand );
649 0           $self->srem( $key, $rand );
650              
651 0           $count -= 1;
652             }
653              
654 0           return (@res);
655             }
656              
657              
658              
659             =head2 srandmember
660              
661             Fetch the value of a random member from a set.
662              
663             =cut
664              
665             sub srandmember
666             {
667 0     0 1   my ( $self, $key ) = (@_);
668              
669 0 0         if ( !$self->{ 'srandommember' } )
670             {
671             $self->{ 'srandommember' } =
672 0 0         $self->{ 'db' }->prepare(
673             "SELECT val FROM sets where key=? ORDER BY RANDOM() LIMIT 1") or
674             die "Failed to prepare";
675             }
676 0           $self->{ 'srandommember' }->execute($key);
677 0   0       my $x = $self->{ 'srandommember' }->fetchrow_array() || "";
678 0           $self->{ 'srandommember' }->finish();
679              
680 0           return ($x);
681             }
682              
683              
684             =head2 sunion
685              
686             Return the values which are present in each of the sets named, duplicates
687             will only be returned one time.
688              
689             For example:
690              
691             =for example begin
692              
693             $redis->sadd( "one", 1 );
694             $redis->sadd( "one", 2 );
695             $redis->sadd( "one", 3 );
696              
697             $redis->sadd( "two", 2 );
698             $redis->sadd( "two", 3 );
699             $redis->sadd( "two", 4 );
700              
701             $redis->sunion( "one", "two" ); # -> [ 1,2,3,4 ]
702              
703             =for example end
704              
705             =cut
706              
707             sub sunion
708             {
709 0     0 1   my ( $self, @keys ) = (@_);
710              
711              
712 0           my %result;
713              
714 0           foreach my $key (@keys)
715             {
716 0           my @vals = $self->smembers($key);
717 0           foreach my $val (@vals)
718             {
719 0           $result{ $val } += 1;
720             }
721             }
722              
723 0           return ( CORE::keys(%result) );
724             }
725              
726              
727             =head2 sunionstore
728              
729             Store the values which are present in each of the named sets in a new set.
730              
731             =cut
732              
733             sub sunionstore
734             {
735 0     0 1   my ( $self, $dest, @keys ) = (@_);
736              
737             # Get the union
738 0           my @union = $self->sunion(@keys);
739              
740             # Delete the current contents of the destination.
741 0           $self->del($dest);
742              
743             # Now store the members
744 0           foreach my $ent (@union)
745             {
746 0           $self->sadd( $dest, $ent );
747             }
748              
749             # Return the number of entries added
750 0           return ( scalar @union );
751             }
752              
753              
754             =head2 sinter
755              
756             Return only those members who exist in each of the named sets.
757              
758             =for example begin
759              
760             $redis->sadd( "one", 1 );
761             $redis->sadd( "one", 2 );
762             $redis->sadd( "one", 3 );
763              
764             $redis->sadd( "two", 2 );
765             $redis->sadd( "two", 3 );
766             $redis->sadd( "two", 4 );
767              
768             $redis->sinter( "one", "two" ); # -> [ 2,3 ]
769              
770             =for example end
771              
772             =cut
773              
774             sub sinter
775             {
776 0     0 1   my ( $self, @names ) = (@_);
777              
778 0           my %seen;
779              
780 0           foreach my $key (@names)
781             {
782 0           my @vals = $self->smembers($key);
783 0           foreach my $val (@vals)
784             {
785 0           $seen{ $val } += 1;
786             }
787             }
788              
789 0           my @result;
790              
791 0           foreach my $key ( CORE::keys(%seen) )
792             {
793 0 0         if ( $seen{ $key } == scalar @names )
794             {
795 0           push( @result, $key );
796             }
797             }
798 0           return (@result);
799             }
800              
801              
802              
803             =head2 sinterstore
804              
805             Store those members who exist in all the named sets in a new set.
806              
807             =cut
808              
809             sub sinterstore
810             {
811 0     0 1   my ( $self, $dest, @names ) = (@_);
812              
813             # Get the values that intersect
814 0           my @update = $self->sinter(@names);
815              
816             # Delete the current contents of the destination.
817 0           $self->del($dest);
818              
819             # Now store the members
820 0           foreach my $ent (@update)
821             {
822 0           $self->sadd( $dest, $ent );
823             }
824              
825             # Return the number of entries added
826 0           return ( scalar @update );
827             }
828              
829              
830             =head2 scard
831              
832             Count the number of entries in the given set.
833              
834             =cut
835              
836             sub scard
837             {
838 0     0 1   my ( $self, $key ) = (@_);
839              
840 0 0         if ( !$self->{ 'scard' } )
841             {
842             $self->{ 'scard' } =
843 0           $self->{ 'db' }->prepare("SELECT COUNT(id) FROM sets where key=?");
844             }
845 0           $self->{ 'scard' }->execute($key);
846 0   0       my $count = $self->{ 'scard' }->fetchrow_array() || 0;
847 0           $self->{ 'scard' }->finish();
848              
849 0           return ($count);
850             }
851              
852              
853              
854             our $AUTOLOAD;
855              
856             sub AUTOLOAD
857             {
858 0     0     my $command = $AUTOLOAD;
859 0           $command =~ s/.*://;
860 0           warn "NOT IMPLEMENTED:$command";
861              
862 0           return 1;
863             }
864              
865             1;
866              
867              
868              
869             =head1 AUTHOR
870              
871             Steve Kemp
872              
873             http://www.steve.org.uk/
874              
875             =cut
876              
877              
878              
879             =head1 LICENSE
880              
881             Copyright (c) 2016 by Steve Kemp. All rights reserved.
882              
883             This module is free software;
884             you can redistribute it and/or modify it under
885             the same terms as Perl itself.
886             The LICENSE file contains the full text of the license.
887              
888             =cut