File Coverage

blib/lib/DBIx/DataModel/Schema.pm
Criterion Covered Total %
statement 222 238 93.2
branch 83 114 72.8
condition 28 42 66.6
subroutine 51 53 96.2
pod 5 8 62.5
total 389 455 85.4


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 20     20   12936 use warnings;
  20         47  
  20         1339  
9 20     20   138 use strict;
  20         80  
  20         633  
10 20     20   99 use DBIx::DataModel::Meta::Utils qw/does/;
  20         38  
  20         1157  
11 20     20   13351 use DBIx::DataModel::Source::Table;
  20         61  
  20         961  
12 20     20   144 use DBIx::DataModel::Carp;
  20         39  
  20         101  
13              
14 20     20   1118 use Scalar::Util qw/blessed/;
  20         41  
  20         1170  
15 20     20   13101 use Data::Structure::Util; # for calling unbless(), fully qualified
  20         78896  
  20         1316  
16 20     20   152 use Module::Load qw/load/;
  20         64  
  20         198  
17 20         1865 use Params::Validate qw/validate_with SCALAR ARRAYREF CODEREF UNDEF
18 20     20   1695 OBJECT BOOLEAN/;
  20         45  
19 20     20   135 use SQL::Abstract::More 1.41;
  20         595  
  20         158  
20 20     20   1069 use Try::Tiny;
  20         42  
  20         1263  
21 20     20   117 use Devel::StackTrace;
  20         40  
  20         536  
22 20     20   91 use mro qw/c3/;
  20         36  
  20         143  
23              
24 20     20   625 use namespace::clean;
  20         36  
  20         129  
25              
26             my $schema_attributes_spec = {
27             dbh => {type => OBJECT|ARRAYREF, optional => 1 },
28             debug => {type => OBJECT|SCALAR, optional => 1 },
29             sql_abstract => {type => OBJECT, optional => 1, isa => 'SQL::Abstract::More'},
30             dbi_prepare_method => {type => SCALAR, default => 'prepare' },
31             placeholder_prefix => {type => SCALAR, default => '?:' },
32             select_implicitly_for => {type => SCALAR, default => '' },
33             autolimit_firstrow => {type => BOOLEAN, optional => 1 },
34             db_schema => {type => SCALAR, optional => 1 },
35             auto_show_error_statement => {type => BOOLEAN, default => 1 },
36             frame_filter => {type => CODEREF, optional => 1 },
37             handleError_policy => {type => SCALAR, default => 'combine', regex => qr(^(if_absent
38             |combine
39             |override
40             |none)$)x },
41             };
42              
43              
44             sub new {
45 19     19 0 99594 my $class = shift;
46              
47 19         90 my %args = @_;
48 19         63 my $dbh = delete $args{dbh}; # this arg needs special treatment
49              
50             # setup metaclass
51 19         84 my $metadm = $class->metadm;
52             not $metadm->{singleton}
53 19 50       88 or croak "$class is already used in single-schema mode, can't call new()";
54              
55             # validate params and create $self
56 19         786 my $self = validate_with(
57             params => [%args],
58             spec => $schema_attributes_spec,
59             allow_extra => 0,
60             );
61 19         156 bless $self, $class;
62              
63             # default SQLA
64 19   66     275 $self->{sql_abstract} ||= $metadm->sql_abstract_class->new($metadm->sql_abstract_args);
65              
66             # from now on, singleton mode will be forbidden
67 19         5539 $metadm->{singleton} = undef;
68              
69             # initial dbh if it was passed within %args;
70 19 100       93 $self->dbh($dbh) if $dbh;
71              
72 19         86 return $self;
73             }
74              
75              
76             # install simple-minded rw accessors for schema attributes
77             foreach my $accessor (grep {$_ ne 'dbh'} keys %$schema_attributes_spec) {
78 20     20   16313 no strict 'refs';
  20         43  
  20         5392  
79             *$accessor = sub {
80 1558     1558   8964 my $self = shift;
81 1558 100       2979 ref $self or $self = $self->singleton;
82              
83 1558 100       2781 if (@_) {
84 9 100       31 if (not defined $_[0]) { # $schema->$attribute(undef) means deleting that attribute in $schema
85 1         6 delete $self->{$accessor};
86             }
87             else {
88             my ($new_val) = validate_with(params => \@_,
89 8         204 spec => [ $schema_attributes_spec->{$accessor} ],
90             allow_extra => 0,
91             called => $accessor,
92             );
93 8         101 $self->{$accessor} = $new_val;
94             }
95             }
96 1558         5715 return $self->{$accessor};
97             };
98             }
99              
100              
101              
102              
103              
104              
105             # proxy methods, forwarded to the meta-schema
106             foreach my $method (qw/Table View Association Composition Type/) {
107 20     20   166 no strict 'refs';
  20         79  
  20         26538  
108             *{$method} = sub {
109 90     90   140762 my $class = shift;
110 90 50       261 not ref $class or croak "$method() is a class method";
111 90         330 $class->metadm->$method(@_);
112             }
113             }
114              
115              
116             sub singleton {
117 927     927 1 205668 my $class = shift;
118 927         2204 my $metadm = $class->metadm;
119              
120 927 100       3231 if (!$metadm->{singleton}) {
    50          
121             not exists $metadm->{singleton}
122 18 50       80 or croak "attempt to call a class method in single-schema mode after "
123             . "Schema::new() has been called; instead, use an instance "
124             . "method : \$schema->table(\$name)->method(...)";
125 18         147 $metadm->{singleton} = $class->new(@_);
126 18         80 $metadm->{singleton}{is_singleton} = 1;
127             }
128             elsif (@_) {
129 0         0 croak "can't pass args to ->singleton(..) after first call";
130             }
131 927         3221 return $metadm->{singleton};
132             }
133              
134              
135              
136             #----------------------------------------------------------------------
137             # RUNTIME METHODS
138             #----------------------------------------------------------------------
139              
140             sub dbh {
141 366     366 1 824593 my ($self, $dbh, %dbh_options) = @_;
142              
143 366 100       1033 ref $self or $self = $self->singleton;
144              
145             # if some args, then this is a "setter" (updating the dbh)
146 366 100       876 if (@_ > 1) {
147              
148             # also support syntax ->dbh([$dbh, %dbh_options])
149 36 100 66     127 ($dbh, %dbh_options) = @$dbh
150             if does($dbh, 'ARRAY') && ! keys %dbh_options;
151              
152             # forbid change of dbh while doing a transaction
153             not $self->{dbh} or $self->{dbh}[0]{AutoCommit}
154 36 50 66     2186 or croak "cannot change dbh(..) while in a transaction";
155              
156 36 100       253 if ($dbh) {
157             # $dbh must be a database handle
158 34 50       222 $dbh->isa('DBI::db')
159             or croak "invalid dbh argument";
160              
161             # only accept $dbh with RaiseError set
162             $dbh->{RaiseError}
163 34 50       417 or croak "arg to dbh(..) must have RaiseError=1";
164              
165             # set ShowErrorStatement if necessary
166             $dbh->{ShowErrorStatement} or !$self->auto_show_error_statement
167 34 100 66     1003 or $dbh->{ShowErrorStatement} = 1;
168              
169             # decide if we should install a HandleError attribute so that error reporting goes through Carp::Object
170 34         816 my $HE_policy = $self->handleError_policy;
171 34         158 my $prev_handler = $dbh->{HandleError};
172 34 50       414 my $should_install = $HE_policy eq 'none' ? 0
    100          
    50          
    100          
173             : $HE_policy eq 'if_absent' ? !$prev_handler
174             : $HE_policy eq 'combine' ? 1
175             : $HE_policy eq 'override' ? 1
176             : die "unexpected value for 'handleError_policy': $HE_policy";
177              
178             # actually, no need to re8install if the previous handler on this $dbh was already installed by the present module
179 34 100 50     340 $should_install &&= 0 if ($prev_handler || -1) == ($dbh->{private_dbix_datamodel_handle_error} || -2);
      100        
      100        
180              
181             # install the handler
182 34 100       504 if ($should_install) {
183 24   100     90 my $must_combine = $prev_handler && $HE_policy eq 'combine';
184 2     2   145 my $new_handler = $must_combine ? sub {my $was_handled = $prev_handler->(@_);
185 0 0       0 die $self->_handle_SQL_error(@_) if !$was_handled; }
186 24 100   4   160 : sub {die $self->_handle_SQL_error(@_)};
  4         359  
187 24         146 $dbh->{HandleError} = $new_handler;
188 24         496 $dbh->{private_dbix_datamodel_handle_error} = $new_handler;
189             }
190              
191             # default values for $dbh_options{returning_through}
192 34 50       460 if (not exists $dbh_options{returning_through}) {
193 34         162 for ($dbh->{Driver}{Name}) {
194 34 50       720 /^Oracle/ and do {$dbh_options{returning_through} = 'INOUT'; last};
  0         0  
  0         0  
195 34 50       167 /^Pg/ and do {$dbh_options{returning_through} = 'FETCH'; last};
  0         0  
  0         0  
196             }
197             }
198              
199             # store the dbh
200 34         542 $self->{dbh} = [$dbh, %dbh_options];
201             }
202             else {
203             # $dbh was explicitly undef, so remove previous dbh
204 2         7 delete $self->{dbh};
205             }
206             }
207              
208 366   100     939 my $return_dbh = $self->{dbh} || [];
209 366 100       3339 return wantarray ? @$return_dbh : $return_dbh->[0];
210             }
211              
212              
213             sub _handle_SQL_error {
214 4     4   9 my ($self, $dbi_errstr, $dbh, $unused) = @_;
215              
216             # skip intermediate ORM stack frames so that errors are reported from the caller's perspective
217             local %DBIx::DataModel::Carp::CARP_OBJECT_CONSTRUCTOR = (frame_filter => sub {
218 44     44   4023 my ($frame_ref) = @_;
219 44         50 my $pkg = $frame_ref->{caller}[0];
220 44 100 66     94 return 0 if $pkg =~ /^DBIx::DataModel/ or $pkg =~ /^SQL::Abstract/; # skip packages used by DBIx::DataModel
221 12 50       20 return $self->{frame_filter}->($frame_ref) if $self->{frame_filter}; # skip packages specified by client
222 12         21 return 1; # otherwise, don't skip
223 4         19 });
224              
225             # re-inject $dbi_errstr also into DBI handles, because some upper levels like DBIx::RetryOverDisconnects
226             # may ignore the error raised by croak and use DBI::errstr instead -- not what we want here !
227 20     20   170 no warnings 'uninitialized';
  20         59  
  20         16409  
228 4 50 33     83 $dbh->set_err($DBI::err, $dbi_errstr) if $DBI::err and $dbi_errstr ne $DBI::errstr;
229              
230             # raise the error through Carp::Object, which will automatically apply the frame filter just set above
231 4         14 croak $dbi_errstr;
232             }
233              
234            
235              
236             sub with_db_schema {
237 0     0 0 0 my ($self, $db_schema) = @_;
238 0 0       0 ref $self or $self = $self->singleton;
239              
240             # return a shallow copy of $self with db_schema set to the given arg
241 0         0 return bless { %$self, db_schema => $db_schema}, ref $self;
242             }
243              
244              
245             my @default_state_components = qw/dbh debug select_implicitly_for
246             dbi_prepare_method db_schema/;
247              
248             sub localize_state {
249 3     3 1 11 my ($self, @components) = @_;
250 3 100       38 ref $self or $self = $self->singleton;
251              
252 3 100       25 @components = @default_state_components unless @components;
253              
254 3         9 my %saved_state;
255 3         18 $saved_state{$_} = $self->{$_} foreach @components;
256              
257 3         28 return DBIx::DataModel::Schema::_State->new($self, \%saved_state);
258             }
259              
260              
261              
262              
263             sub do_after_commit {
264 3     3 0 404 my ($self, $coderef) = @_;
265 3 50       12 ref $self or $self = $self->singleton;
266              
267             $self->{transaction_dbhs}
268 3 50       7 or croak "do_after_commit() called outside of a transaction";
269 3         4 push @{$self->{after_commit_callbacks}}, $coderef;
  3         7  
270             }
271              
272              
273             sub do_transaction {
274 25     25 1 4894 my ($self, $coderef, @new_dbh) = @_;
275 25 50       91 ref $self or $self = $self->singleton;
276              
277 25 50       66 does($coderef, 'CODE')
278             or croak 'first arg to $schema->do_transaction(...) should be a coderef';
279              
280 25   100     233 my $transaction_dbhs = $self->{transaction_dbhs} ||= [];
281              
282             # localize the dbh and its options, if so requested.
283             my $local_state = $self->localize_state(qw/dbh/)
284             and
285 25 100 33     64 delete($self->{dbh}), # cheat so that dbh() does not complain
286             $self->dbh(@new_dbh) # and now update the dbh
287             if @new_dbh; # postfix "if" because $local_state must not be in a block
288              
289             # check that we have a dbh
290 25 50       47 my $dbh = $self->dbh
291             or croak "no database handle for transaction";
292              
293             # how to call and how to return will depend on context
294 25 100       56 my $want = wantarray ? "array" : defined(wantarray) ? "scalar" : "void";
    100          
295             my $in_context = {
296 25         32 array => do {my @array;
297 8     8   23 {call => sub {@array = $coderef->()},
298 25     8   152 return => sub {return @array}}},
  8         84  
299 25         29 scalar => do {my $scalar;
300 12     12   28 {call => sub {$scalar = $coderef->()},
301 25     12   204 return => sub {return $scalar}}},
  12         89  
302 5     5   18 void => {call => sub {$coderef->()},
303 0     0   0 return => sub {return}}
304 25         29 }->{$want};
305              
306              
307             my $begin_work_and_exec = sub {
308             # make sure dbh is in transaction mode
309 25 100   25   89 if ($dbh->{AutoCommit}) {
310 15         220 $dbh->begin_work; # will set AutoCommit to false
311 15         4852 push @$transaction_dbhs, $dbh;
312             }
313              
314             # do the real work
315 25         223 $in_context->{call}->();
316 25         170 };
317              
318 25 100       47 if (@$transaction_dbhs) { # if in a nested transaction, just exec
319 12         18 $begin_work_and_exec->();
320             }
321             else { # else try to execute and commit in an eval block
322              
323             # support for DBIx::RetryOverDisconnects: decide how many retries
324 13         19 my $n_retries = 1;
325 13 50       127 if ($dbh->isa('DBIx::RetryOverDisconnects::db')) {
326 0         0 $n_retries = $dbh->{DBIx::RetryOverDisconnects::PRIV()}{txn_retries};
327             }
328              
329             # try to do the transaction, maybe several times in cas of disconnection
330             RETRY:
331 13         36 for my $retry (1 .. $n_retries) {
332 20     20   158 no warnings 'exiting'; # because "last/next" are in Try::Tiny subroutines
  20         36  
  20         11440  
333             try {
334             # check AutoCommit state
335             $dbh->{AutoCommit}
336 13 50   13   622 or croak "dbh was not in Autocommit mode before initial transaction";
337              
338             # execute the transaction
339 13         255 $begin_work_and_exec->();
340              
341             # commit all dbhs and then reset the list of dbhs
342 8         73 $_->commit foreach @$transaction_dbhs;
343 8         2591 delete $self->{transaction_dbhs};
344              
345 8         42 last RETRY; # transaction successful, get out of the loop
346             }
347             catch {
348 5     5   140 my $err = $_;
349              
350             # if this was a disconnection ..
351 5 50 33     40 if ($dbh->isa('DBIx::RetryOverDisconnects::db')
352             # $dbh->can() is broken on DBI handles, so use ->isa() instead
353             && $dbh->is_trans_disconnect) {
354 0         0 $transaction_dbhs = [];
355 0 0       0 next RETRY if $retry < $n_retries; # .. try again
356 0         0 $self->exc_conn_trans_fatal->throw; # .. or no hope (and no rollback)
357             }
358              
359             # otherwise, for regular SQL errors, try to rollback and then throw
360 5         7 my @rollback_errs;
361 5         10 foreach my $dbh (reverse @$transaction_dbhs) {
362 5         206 try {$dbh->rollback}
363 5         31 catch {push @rollback_errs, $_};
  0         0  
364             }
365 5         1602 delete $self->{transaction_dbhs};
366 5         12 delete $self->{after_commit_callbacks};
367 5         28 DBIx::DataModel::Schema::_Exception->throw($err, @rollback_errs);
368 13         90 };
369             }
370             }
371              
372             # execute the after_commit callbacks
373 20   100     601 my $callbacks = delete $self->{after_commit_callbacks} || [];
374 20         45 $_->() foreach @$callbacks;
375              
376 20         42 return $in_context->{return}->();
377             }
378              
379              
380             sub unbless {
381 32     32 1 79 my $class = shift;
382 32         151 Data::Structure::Util::unbless($_) foreach @_;
383              
384 32 100       361 return wantarray ? @_ : $_[0];
385             }
386              
387              
388             # accessors to connected sources (tables or joins) from the current schema
389             # local method metadm method
390             # ============ =============
391             my %accessor_map = (table => 'table',
392             join => 'define_join',
393             db_table => 'db_table');
394             while (my ($local, $remote) = each %accessor_map) {
395 20     20   180 no strict 'refs';
  20         60  
  20         7784  
396             *$local = sub {
397 120     120   741341 my $self = shift;
398 120 100       804 ref $self or $self = $self->singleton;
399              
400 120 100       407 my $meta_source = $self->metadm->$remote(@_) or return;
401 117         1382 my $obj = bless {__schema => $self}, $meta_source->class;
402 117         1501 return $obj;
403             }
404             }
405              
406             #----------------------------------------------------------------------
407             # UTILITY FUNCTIONS (PRIVATE)
408             #----------------------------------------------------------------------
409              
410              
411             sub _debug { # internal method to send debug messages
412 227     227   476 my ($self, $msg) = @_;
413 227         2606 my $debug = $self->debug;
414 227 100       669 if ($debug) {
415 1 50 33     10 if (ref $debug && $debug->can('debug')) { $debug->debug($msg) }
  1         3  
416 0         0 else { carp $msg; }
417             }
418             }
419              
420              
421              
422              
423              
424             #----------------------------------------------------------------------
425             # PRIVATE CLASS FOR LOCALIZING STATE (see L method
426             #----------------------------------------------------------------------
427              
428             package
429             DBIx::DataModel::Schema::_State;
430              
431             sub new {
432 3     3   21 my ($class, $schema, $state) = @_;
433 3         51 bless [$schema, $state], $class;
434             }
435              
436              
437             sub DESTROY { # called when the guard goes out of scope
438 3     3   16 my ($self) = @_;
439              
440             # localize $@, in case we were called while dying - see L
441 3         8 local $@;
442              
443 3         12 my ($schema, $previous_state) = @$self;
444              
445             # must cleanup dbh so that ->dbh(..) does not complain if in a transaction
446 3 50       14 delete $schema->{dbh} if exists $previous_state->{dbh};
447            
448             # invoke "setter" method on each state component
449 3         26 while (my ($k, $v) = each %$previous_state) {
450 7 100       49 $schema->$k($v) if $v;
451             }
452             }
453              
454              
455             #----------------------------------------------------------------------
456             # PRIVATE CLASS FOR TRANSACTION EXCEPTIONS
457             #----------------------------------------------------------------------
458              
459             package
460             DBIx::DataModel::Schema::_Exception;
461 20     20   166 use strict;
  20         43  
  20         669  
462 20     20   113 use warnings;
  20         33  
  20         2976  
463              
464             use overload '""' => sub {
465 4     4   66 my $self = shift;
466 4         9 my $err = $self->initial_error;
467 4         8 my @rollback_errs = $self->rollback_errors;
468 4 50       10 my $rollback_status = @rollback_errs ? CORE::join(", ", @rollback_errs) : "OK";
469 4         28 return "FAILED TRANSACTION: $err (rollback: $rollback_status)";
470 20     20   155 };
  20         40  
  20         250  
471              
472              
473             sub throw {
474 5     5   11 my $class = shift;
475 5         16 my $self = bless [@_], $class;
476 5         98 die $self;
477             }
478              
479             sub initial_error {
480 6     6   20 my $self = shift;
481 6         26 return $self->[0];
482             }
483              
484             sub rollback_errors {
485 6     6   10 my $self = shift;
486 6         11 return @$self[1..$#{$self}];
  6         20  
487             }
488              
489              
490             1;
491              
492             __END__