File Coverage

blib/lib/DBIx/DataModel/Schema.pm
Criterion Covered Total %
statement 189 206 91.7
branch 56 84 66.6
condition 15 27 55.5
subroutine 45 47 95.7
pod 5 8 62.5
total 310 372 83.3


line stmt bran cond sub pod time code
1             #----------------------------------------------------------------------
2             package DBIx::DataModel::Schema;
3             #----------------------------------------------------------------------
4              
5             # see POD doc at end of file
6             # version : see DBIx::DataModel
7              
8 18     18   8185 use warnings;
  18         50  
  18         668  
9 18     18   105 use strict;
  18         49  
  18         456  
10 18     18   106 use DBIx::DataModel::Meta::Utils qw/does/;
  18         78  
  18         907  
11 18     18   9431 use DBIx::DataModel::Source::Table;
  18         50  
  18         655  
12              
13 18     18   136 use Scalar::Util qw/blessed/;
  18         44  
  18         1074  
14 18     18   9381 use Data::Structure::Util; # for calling unbless(), fully qualified
  18         60579  
  18         1003  
15 18     18   155 use Module::Load qw/load/;
  18         38  
  18         124  
16 18         1318 use Params::Validate qw/validate_with SCALAR ARRAYREF CODEREF UNDEF
17 18     18   1207 OBJECT BOOLEAN/;
  18         41  
18              
19 18     18   126 use Carp::Clan qw[^(DBIx::DataModel::|SQL::Abstract)];
  18         46  
  18         155  
20              
21 18     18   1686 use SQL::Abstract::More 1.39;
  18         554  
  18         144  
22 18     18   807 use Try::Tiny;
  18         63  
  18         1188  
23 18     18   137 use mro qw/c3/;
  18         55  
  18         141  
24              
25 18     18   575 use namespace::clean;
  18         36  
  18         120  
26              
27              
28             my $spec = {
29             dbh => {type => OBJECT|ARRAYREF, optional => 1},
30             debug => {type => OBJECT|SCALAR, optional => 1},
31             sql_abstract => {type => OBJECT,
32             isa => 'SQL::Abstract::More',
33             optional => 1},
34             dbi_prepare_method => {type => SCALAR, default => 'prepare'},
35             placeholder_prefix => {type => SCALAR, default => '?:'},
36             select_implicitly_for => {type => SCALAR, default => ''},
37             autolimit_firstrow => {type => BOOLEAN, optional => 1},
38             db_schema => {type => SCALAR, optional => 1},
39             resultAs_classes => {type => ARRAYREF, optional => 1},
40             };
41              
42              
43              
44             sub new {
45 17     17 0 206 my $class = shift;
46              
47             not $class->metadm->{singleton}
48 17 50       61 or croak "$class is already used in single-schema mode, can't call new()";
49              
50             # validate params
51 17         713 my %params = validate_with(
52             params => \@_,
53             spec => $spec,
54             allow_extra => 0,
55             );
56              
57             # instantiate and call 'setter' methods for %params
58 17         199 my $self = bless {}, $class;
59 17         114 while (my ($method, $arg) = each %params) {
60 56         347 $self->$method($arg);
61             }
62              
63             # default SQLA
64 17   66     223 $self->{sql_abstract} ||= SQL::Abstract::More->new;
65              
66             # default resultAs_classes
67 17   33     4680 $self->{resultAs_classes} ||= mro::get_linear_isa($class);
68              
69             # from now on, singleton mode will be forbidden
70 17         84 $class->metadm->{singleton} = undef;
71              
72 17         84 return $self;
73             }
74              
75              
76             # proxy methods, forwarded to the meta-schema
77             foreach my $method (qw/Table View Association Composition Type/) {
78 18     18   12005 no strict 'refs';
  18         42  
  18         10232  
79             *{$method} = sub {
80 86     86   13488 my $class = shift;
81 86 50       210 not ref $class or croak "$method() is a class method";
82 86         251 $class->metadm->$method(@_);
83             }
84             }
85              
86              
87             sub singleton {
88 823     823 1 11631 my $class = shift;
89 823         1925 my $metadm = $class->metadm;
90              
91 823 100       2738 if (!$metadm->{singleton}) {
    50          
92             not exists $metadm->{singleton}
93 16 50       85 or croak "attempt to call a class method in single-schema mode after "
94             . "Schema::new() has been called; instead, use an instance "
95             . "method : \$schema->table(\$name)->method(...)";
96 16         114 $metadm->{singleton} = $class->new(@_);
97 16         62 $metadm->{singleton}{is_singleton} = 1;
98             }
99             elsif (@_) {
100 0         0 croak "can't pass args to ->singleton(..) after first call";
101             }
102 823         3073 return $metadm->{singleton};
103             }
104              
105              
106              
107             #----------------------------------------------------------------------
108             # RUNTIME METHODS
109             #----------------------------------------------------------------------
110              
111             sub dbh {
112 331     331 1 52056 my ($self, $dbh, %dbh_options) = @_;
113              
114 331 100       922 ref $self or $self = $self->singleton;
115              
116             # if some args, then this is a "setter" (updating the dbh)
117 331 100       823 if (@_ > 1) {
118              
119             # also support syntax ->dbh([$dbh, %dbh_options])
120 26 100 66     112 ($dbh, %dbh_options) = @$dbh
121             if does($dbh, 'ARRAY') && ! keys %dbh_options;
122              
123             # forbid change of dbh while doing a transaction
124             not $self->{dbh} or $self->{dbh}[0]{AutoCommit}
125 26 50 66     1912 or croak "cannot change dbh(..) while in a transaction";
126              
127 26 100       215 if ($dbh) {
128             # $dbh must be a database handle
129 24 50       190 $dbh->isa('DBI::db')
130             or croak "invalid dbh argument";
131              
132             # only accept $dbh with RaiseError set
133             $dbh->{RaiseError}
134 24 50       336 or croak "arg to dbh(..) must have RaiseError=1";
135              
136             # default values for $dbh_options{returning_through}
137 24 50       893 if (not exists $dbh_options{returning_through}) {
138 24         141 for ($dbh->{Driver}{Name}) {
139 24 50       612 /^Oracle/ and do {$dbh_options{returning_through} = 'INOUT'; last};
  0         0  
  0         0  
140 24 50       112 /^Pg/ and do {$dbh_options{returning_through} = 'FETCH'; last};
  0         0  
  0         0  
141             }
142             }
143              
144             # store the dbh
145 24         145 $self->{dbh} = [$dbh, %dbh_options];
146             }
147             else {
148             # $dbh was explicitly undef, so remove previous dbh
149 2         6 delete $self->{dbh};
150             }
151             }
152              
153 331   100     898 my $return_dbh = $self->{dbh} || [];
154 331 100       1882 return wantarray ? @$return_dbh : $return_dbh->[0];
155             }
156              
157              
158              
159             # some rw setters/getters
160             my @accessors = qw/debug select_implicitly_for dbi_prepare_method
161             sql_abstract placeholder_prefix autolimit_firstrow
162             db_schema resultAs_classes/;
163             foreach my $accessor (@accessors) {
164 18     18   154 no strict 'refs';
  18         105  
  18         13166  
165             *$accessor = sub {
166 1459     1459   2585 my $self = shift;
167 1459 50       3182 ref $self or $self = $self->singleton;
168              
169 1459 100       2905 if (@_) {
170 62         224 $self->{$accessor} = shift;
171             }
172 1459         5400 return $self->{$accessor};
173             };
174             }
175              
176              
177             sub with_db_schema {
178 0     0 0 0 my ($self, $db_schema) = @_;
179 0 0       0 ref $self or $self = $self->singleton;
180              
181             # return a shallow copy of $self with db_schema set to the given arg
182 0         0 return bless { %$self, db_schema => $db_schema}, ref $self;
183             }
184              
185              
186             my @default_state_components = qw/dbh debug select_implicitly_for
187             dbi_prepare_method db_schema/;
188              
189             sub localize_state {
190 3     3 1 13 my ($self, @components) = @_;
191 3 100       17 ref $self or $self = $self->singleton;
192              
193 3 100       18 @components = @default_state_components unless @components;
194              
195 3         9 my %saved_state;
196 3         49 $saved_state{$_} = $self->{$_} foreach @components;
197              
198 3         34 return DBIx::DataModel::Schema::_State->new($self, \%saved_state);
199             }
200              
201              
202              
203              
204             sub do_after_commit {
205 3     3 0 452 my ($self, $coderef) = @_;
206 3 50       14 ref $self or $self = $self->singleton;
207              
208             $self->{transaction_dbhs}
209 3 50       8 or croak "do_after_commit() called outside of a transaction";
210 3         6 push @{$self->{after_commit_callbacks}}, $coderef;
  3         12  
211             }
212              
213              
214             sub do_transaction {
215 25     25 1 6040 my ($self, $coderef, @new_dbh) = @_;
216 25 50       95 ref $self or $self = $self->singleton;
217              
218 25 50       75 does($coderef, 'CODE')
219             or croak 'first arg to $schema->do_transaction(...) should be a coderef';
220              
221 25   100     290 my $transaction_dbhs = $self->{transaction_dbhs} ||= [];
222              
223             # localize the dbh and its options, if so requested.
224             my $local_state = $self->localize_state(qw/dbh/)
225             and
226 25 100 33     96 delete($self->{dbh}), # cheat so that dbh() does not complain
227             $self->dbh(@new_dbh) # and now update the dbh
228             if @new_dbh; # postfix "if" because $local_state must not be in a block
229              
230             # check that we have a dbh
231 25 50       57 my $dbh = $self->dbh
232             or croak "no database handle for transaction";
233              
234             # how to call and how to return will depend on context
235 25 100       108 my $want = wantarray ? "array" : defined(wantarray) ? "scalar" : "void";
    100          
236             my $in_context = {
237 25         40 array => do {my @array;
238 8     8   26 {call => sub {@array = $coderef->()},
239 25     8   199 return => sub {return @array}}},
  8         90  
240 25         43 scalar => do {my $scalar;
241 12     12   38 {call => sub {$scalar = $coderef->()},
242 25     12   196 return => sub {return $scalar}}},
  12         107  
243 5     5   21 void => {call => sub {$coderef->()},
244 0     0   0 return => sub {return}}
245 25         37 }->{$want};
246              
247              
248             my $begin_work_and_exec = sub {
249             # make sure dbh is in transaction mode
250 25 100   25   110 if ($dbh->{AutoCommit}) {
251 15         272 $dbh->begin_work; # will set AutoCommit to false
252 15         5305 push @$transaction_dbhs, $dbh;
253             }
254              
255             # do the real work
256 25         247 $in_context->{call}->();
257 25         179 };
258              
259 25 100       84 if (@$transaction_dbhs) { # if in a nested transaction, just exec
260 12         24 $begin_work_and_exec->();
261             }
262             else { # else try to execute and commit in an eval block
263              
264             # support for DBIx::RetryOverDisconnects: decide how many retries
265 13         30 my $n_retries = 1;
266 13 50       101 if ($dbh->isa('DBIx::RetryOverDisconnects::db')) {
267 0         0 $n_retries = $dbh->{DBIx::RetryOverDisconnects::PRIV()}{txn_retries};
268             }
269              
270             # try to do the transaction, maybe several times in cas of disconnection
271             RETRY:
272 13         36 for my $retry (1 .. $n_retries) {
273 18     18   145 no warnings 'exiting'; # because "last/next" are in Try::Tiny subroutines
  18         80  
  18         9256  
274             try {
275             # check AutoCommit state
276             $dbh->{AutoCommit}
277 13 50   13   687 or croak "dbh was not in Autocommit mode before initial transaction";
278              
279             # execute the transaction
280 13         313 $begin_work_and_exec->();
281              
282             # commit all dbhs and then reset the list of dbhs
283 8         72 $_->commit foreach @$transaction_dbhs;
284 8         3384 delete $self->{transaction_dbhs};
285              
286 8         51 last RETRY; # transaction successful, get out of the loop
287             }
288             catch {
289 5     5   338 my $err = $_;
290              
291             # if this was a disconnection ..
292 5 50 33     48 if ($dbh->isa('DBIx::RetryOverDisconnects::db')
293             # $dbh->can() is broken on DBI handles, so use ->isa() instead
294             && $dbh->is_trans_disconnect) {
295 0         0 $transaction_dbhs = [];
296 0 0       0 next RETRY if $retry < $n_retries; # .. try again
297 0         0 $self->exc_conn_trans_fatal->throw; # .. or no hope (and no rollback)
298             }
299              
300             # otherwise, for regular SQL errors, try to rollback and then throw
301 5         10 my @rollback_errs;
302 5         11 foreach my $dbh (reverse @$transaction_dbhs) {
303 5         268 try {$dbh->rollback}
304 5         53 catch {push @rollback_errs, $_};
  0         0  
305             }
306 5         1824 delete $self->{transaction_dbhs};
307 5         9 delete $self->{after_commit_callbacks};
308 5         36 DBIx::DataModel::Schema::_Exception->throw($err, @rollback_errs);
309 13         116 };
310             }
311             }
312              
313             # execute the after_commit callbacks
314 20   100     616 my $callbacks = delete $self->{after_commit_callbacks} || [];
315 20         48 $_->() foreach @$callbacks;
316              
317 20         55 return $in_context->{return}->();
318             }
319              
320              
321             sub unbless {
322 32     32 1 83 my $class = shift;
323 32         136 Data::Structure::Util::unbless($_) foreach @_;
324              
325 32 100       413 return wantarray ? @_ : $_[0];
326             }
327              
328              
329             # accessors to connected sources (tables or joins) from the current schema
330             # local method metadm method
331             # ============ =============
332             my %accessor_map = (table => 'table',
333             join => 'define_join',
334             db_table => 'db_table');
335             while (my ($local, $remote) = each %accessor_map) {
336 18     18   150 no strict 'refs';
  18         44  
  18         6422  
337             *$local = sub {
338 101     101   470800 my $self = shift;
339 101 100       481 ref $self or $self = $self->singleton;
340              
341 101 100       273 my $meta_source = $self->metadm->$remote(@_) or return;
342 98         498 my $obj = bless {__schema => $self}, $meta_source->class;
343 98         1155 return $obj;
344             }
345             }
346              
347             #----------------------------------------------------------------------
348             # UTILITY FUNCTIONS (PRIVATE)
349             #----------------------------------------------------------------------
350              
351              
352             sub _debug { # internal method to send debug messages
353 206     206   485 my ($self, $msg) = @_;
354 206         655 my $debug = $self->debug;
355 206 50       621 if ($debug) {
356 0 0 0     0 if (ref $debug && $debug->can('debug')) { $debug->debug($msg) }
  0         0  
357 0         0 else { carp $msg; }
358             }
359             }
360              
361              
362              
363              
364              
365             #----------------------------------------------------------------------
366             # PRIVATE CLASS FOR LOCALIZING STATE (see L method
367             #----------------------------------------------------------------------
368              
369             package
370             DBIx::DataModel::Schema::_State;
371              
372             sub new {
373 3     3   11 my ($class, $schema, $state) = @_;
374 3         33 bless [$schema, $state], $class;
375             }
376              
377              
378             sub DESTROY { # called when the guard goes out of scope
379 3     3   18 my ($self) = @_;
380              
381             # localize $@, in case we were called while dying - see L
382 3         8 local $@;
383              
384 3         13 my ($schema, $previous_state) = @$self;
385              
386             # must cleanup dbh so that ->dbh(..) does not complain if in a transaction
387 3 50       35 if (exists $previous_state->{dbh}) {
388 3         13 delete $schema->{dbh};
389             }
390              
391             # invoke "setter" method on each state component
392 3         35 $schema->$_($previous_state->{$_}) foreach keys %$previous_state;
393             }
394              
395              
396             #----------------------------------------------------------------------
397             # PRIVATE CLASS FOR TRANSACTION EXCEPTIONS
398             #----------------------------------------------------------------------
399              
400             package
401             DBIx::DataModel::Schema::_Exception;
402 18     18   149 use strict;
  18         59  
  18         500  
403 18     18   106 use warnings;
  18         66  
  18         2242  
404              
405             use overload '""' => sub {
406 4     4   103 my $self = shift;
407 4         14 my $err = $self->initial_error;
408 4         13 my @rollback_errs = $self->rollback_errors;
409 4 50       13 my $rollback_status = @rollback_errs ? join(", ", @rollback_errs) : "OK";
410 4         37 return "FAILED TRANSACTION: $err (rollback: $rollback_status)";
411 18     18   145 };
  18         40  
  18         282  
412              
413              
414             sub throw {
415 5     5   12 my $class = shift;
416 5         15 my $self = bless [@_], $class;
417 5         37 die $self;
418             }
419              
420             sub initial_error {
421 6     6   40 my $self = shift;
422 6         40 return $self->[0];
423             }
424              
425             sub rollback_errors {
426 6     6   16 my $self = shift;
427 6         12 return @$self[1..$#{$self}];
  6         25  
428             }
429              
430              
431             1;
432              
433             __END__