File Coverage

blib/lib/Starch/Store/DBIx/Connector.pm
Criterion Covered Total %
statement 46 47 97.8
branch 3 6 50.0
condition n/a
subroutine 17 17 100.0
pod 0 1 0.0
total 66 71 92.9


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