File Coverage

blib/lib/Starch/Store/DBI.pm
Criterion Covered Total %
statement 59 62 95.1
branch 6 10 60.0
condition 1 3 33.3
subroutine 17 17 100.0
pod 2 2 100.0
total 85 94 90.4


line stmt bran cond sub pod time code
1             package Starch::Store::DBI;
2             $Starch::Store::DBI::VERSION = '0.02';
3             =head1 NAME
4              
5             Starch::Store::DBI - Starch storage backend using DBI.
6              
7             =head1 SYNOPSIS
8              
9             my $starch = Starch->new(
10             store => {
11             class => '::DBI',
12             dbh => [
13             $dsn,
14             $username,
15             $password,
16             { RaiseError => 1 },
17             ],
18             table => 'my_states',
19             },
20             );
21              
22             =head1 DESCRIPTION
23              
24             This L store uses L to set and get state data.
25              
26             Consider using L instead
27             of this store as L provides superior re-connection
28             and transaction handling capabilities.
29              
30             The table in your database should contain three columns. This
31             is the SQLite syntax for creating a compatible table which you
32             can modify to work for your particular database's syntax:
33              
34             CREATE TABLE starch_states (
35             key TEXT NOT NULL PRIMARY KEY,
36             data TEXT NOT NULL,
37             expiration INTEGER NOT NULL
38             )
39              
40             =cut
41              
42 1     1   24101 use DBI;
  1         24899  
  1         100  
43 1     1   13 use Types::Standard -types;
  1         3  
  1         22  
44 1     1   3713 use Types::Common::String -types;
  1         14  
  1         11  
45 1     1   945 use Scalar::Util qw( blessed );
  1         1  
  1         56  
46 1     1   1630 use Data::Serializer::Raw;
  1         713  
  1         53  
47              
48 1     1   6 use Moo;
  1         1  
  1         10  
49 1     1   362 use strictures 2;
  1         8  
  1         51  
50 1     1   212 use namespace::clean;
  1         2  
  1         7  
51              
52             with qw(
53             Starch::Store
54             );
55              
56             =head1 REQUIRED ARGUMENTS
57              
58             =head2 dbh
59              
60             This must be set to either array ref arguments for L
61             or a pre-built object (often retrieved using a method proxy).
62              
63             When configuring Starch from static configuration files using a
64             L
65             is a good way to link your existing L object constructor
66             in with Starch so that starch doesn't build its own.
67              
68             =cut
69              
70             has _dbh_arg => (
71             is => 'ro',
72             isa => (InstanceOf[ 'DBI::db' ]) | ArrayRef,
73             init_arg => 'dbh',
74             required => 1,
75             );
76              
77             has dbh => (
78             is => 'lazy',
79             isa => InstanceOf[ 'DBI::db' ],
80             init_arg => undef,
81             );
82             sub _build_dbh {
83 4     4   4960 my ($self) = @_;
84              
85 4         76 my $dbh = $self->_dbh_arg();
86 4 50       23 return $dbh if blessed $dbh;
87              
88 4         40 return DBI->connect( @$dbh );
89             }
90              
91             =head1 OPTIONAL ARGUMENTS
92              
93             =head2 serializer
94              
95             A L for serializing the state data for storage
96             in the L. Can be specified as string containing the
97             serializer name, a hash ref of Data::Serializer::Raw arguments, or as a
98             pre-created Data::Serializer::Raw object. Defaults to C.
99              
100             Consider using the C or C serializers for speed.
101              
102             C will likely be the fastest and produce the most compact data.
103              
104             =cut
105              
106             has _serializer_arg => (
107             is => 'ro',
108             isa => ((InstanceOf[ 'Data::Serializer::Raw' ]) | HashRef) | NonEmptySimpleStr,
109             init_arg => 'serializer',
110             default => 'JSON',
111             );
112              
113             has serializer => (
114             is => 'lazy',
115             isa => InstanceOf[ 'Data::Serializer::Raw' ],
116             init_arg => undef,
117             );
118             sub _build_serializer {
119 3     3   1824 my ($self) = @_;
120              
121 3         15 my $serializer = $self->_serializer_arg();
122 3 50       16 return $serializer if blessed $serializer;
123              
124 3 50       8 if (ref $serializer) {
125 0         0 return Data::Serializer::Raw->new( %$serializer );
126             }
127              
128 3         29 return Data::Serializer::Raw->new(
129             serializer => $serializer,
130             );
131             }
132              
133             =head2 table
134              
135             The table name where states are stored in the database.
136             Defaults to C.
137              
138             =cut
139              
140             has table => (
141             is => 'ro',
142             isa => NonEmptySimpleStr,
143             default => 'starch_states',
144             );
145              
146             =head2 key_column
147              
148             The column in the L
where the state ID is stored. 149             Defaults to C. 150               151             =cut 152               153             has key_column => ( 154             is => 'ro', 155             isa => NonEmptySimpleStr, 156             default => 'key', 157             ); 158               159             =head2 data_column 160               161             The column in the L which will hold the state 162             data. Defaults to C. 163               164             =cut 165               166             has data_column => ( 167             is => 'ro', 168             isa => NonEmptySimpleStr, 169             default => 'data', 170             ); 171               172             =head2 expiration_column 173               174             The column in the L which will hold the epoch time 175             when the state should be expired. Defaults to C. 176               177             =cut 178               179             has expiration_column => ( 180             is => 'ro', 181             isa => NonEmptySimpleStr, 182             default => 'expiration', 183             ); 184               185             =head1 ATTRIBUTES 186               187             =head2 insert_sql 188               189             The SQL used to create state data. 190               191             =cut 192               193             has insert_sql => ( 194             is => 'lazy', 195             isa => NonEmptyStr, 196             init_arg => undef, 197             ); 198             sub _build_insert_sql { 199 3     3   839 my ($self) = @_; 200               201 3         92 return sprintf( 202             'INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)', 203             $self->table(), 204             $self->key_column(), 205             $self->data_column(), 206             $self->expiration_column(), 207             ); 208             } 209               210             =head2 update_sql 211               212             The SQL used to update state data. 213               214             =cut 215               216             has update_sql => ( 217             is => 'lazy', 218             isa => NonEmptyStr, 219             init_arg => undef, 220             ); 221             sub _build_update_sql { 222 2     2   901 my ($self) = @_; 223               224 2         89 return sprintf( 225             'UPDATE %s SET %s=?, %s=? WHERE %s=?', 226             $self->table(), 227             $self->data_column(), 228             $self->expiration_column(), 229             $self->key_column(), 230             ); 231             } 232               233             =head2 exists_sql 234               235             The SQL used to confirm whether state data already exists. 236               237             =cut 238               239             has exists_sql => ( 240             is => 'lazy', 241             isa => NonEmptyStr, 242             init_arg => undef, 243             ); 244             sub _build_exists_sql { 245 3     3   528 my ($self) = @_; 246               247 3         72 return sprintf( 248             'SELECT 1 FROM %s WHERE %s = ?', 249             $self->table(), 250             $self->key_column(), 251             ); 252             } 253               254             =head2 select_sql 255               256             The SQL used to retrieve state data. 257               258             =cut 259               260             has select_sql => ( 261             is => 'lazy', 262             isa => NonEmptyStr, 263             init_arg => undef, 264             ); 265             sub _build_select_sql { 266 3     3   741 my ($self) = @_; 267               268 3         226 return sprintf( 269             'SELECT %s, %s FROM %s WHERE %s = ?', 270             $self->data_column(), 271             $self->expiration_column(), 272             $self->table(), 273             $self->key_column(), 274             ); 275             } 276               277             =head2 delete_sql 278               279             The SQL used to delete state data. 280               281             =cut 282               283             has delete_sql => ( 284             is => 'lazy', 285             isa => NonEmptyStr, 286             init_arg => undef, 287             ); 288             sub _build_delete_sql { 289 2     2   1103 my ($self) = @_; 290               291 2         57 return sprintf( 292             'DELETE FROM %s WHERE %s = ?', 293             $self->table(), 294             $self->key_column(), 295             ); 296             } 297               298             =head1 METHODS 299               300             =head2 set 301               302             Set L. 303               304             =head2 get 305               306             Set L. 307               308             =head2 remove 309               310             Set L. 311               312             =cut 313               314             sub set { 315             my ($self, $id, $namespace, $data, $expires) = @_; 316               317             local $Carp::Interal{ (__PACKAGE__) } = 1; 318               319             my $key = $self->stringify_key( $id, $namespace ); 320               321             my $dbh = $self->dbh(); 322               323             my $sth = $dbh->prepare_cached( 324             $self->exists_sql(), 325             ); 326               327             my ($exists) = $dbh->selectrow_array( $sth, undef, $key ); 328               329             $data = $self->serializer->serialize( $data ); 330             $expires += time(); 331               332             if ($exists) { 333             my $sth = $self->dbh->prepare_cached( 334             $self->update_sql(), 335             ); 336               337             $sth->execute( $data, $expires, $key ); 338             } 339             else { 340             my $sth = $self->dbh->prepare_cached( 341             $self->insert_sql(), 342             ); 343               344             $sth->execute( $key, $data, $expires ); 345             } 346               347             return; 348             } 349               350             sub get { 351 19     19 1 18970 my ($self, $id, $namespace) = @_; 352               353 19         71 local $Carp::Interal{ (__PACKAGE__) } = 1; 354               355 19         102 my $key = $self->stringify_key( $id, $namespace ); 356               357 19         686 my $dbh = $self->dbh(); 358               359 19         608 my $sth = $dbh->prepare_cached( 360             $self->select_sql(), 361             ); 362               363 19         4147 my ($data, $expiration) = $dbh->selectrow_array( $sth, undef, $key ); 364               365 19 100       197 return undef if !defined $data; 366               367 12 50 33     118 if ($expiration and $expiration < time()) { 368 0         0 $self->remove( $id, $namespace ); 369 0         0 return undef; 370             } 371               372 12         495 return $self->serializer->deserialize( $data ); 373             } 374               375             sub remove { 376 6     6 1 11222 my ($self, $id, $namespace) = @_; 377               378 6         23 local $Carp::Interal{ (__PACKAGE__) } = 1; 379               380 6         30 my $key = $self->stringify_key( $id, $namespace ); 381               382 6         299 my $dbh = $self->dbh(); 383               384 6         667 my $sth = $dbh->prepare_cached( 385             $self->delete_sql(), 386             ); 387               388 6         489592 $sth->execute( $key ); 389               390 6         77 return; 391             } 392               393             1; 394             __END__