File Coverage

blib/lib/DBIx/DataModel/Statement.pm
Criterion Covered Total %
statement 330 369 89.4
branch 142 196 72.4
condition 30 57 52.6
subroutine 46 56 82.1
pod 0 27 0.0
total 548 705 77.7


line stmt bran cond sub pod time code
1             #----------------------------------------------------------------------
2             package DBIx::DataModel::Statement;
3             #----------------------------------------------------------------------
4             # see POD doc at end of file
5              
6 13     13   6990 use warnings;
  13         39  
  13         466  
7 13     13   77 use strict;
  13         43  
  13         348  
8 13     13   81 use List::Util qw/min/;
  13         25  
  13         1123  
9 13     13   91 use List::MoreUtils qw/firstval any/;
  13         28  
  13         199  
10 13     13   10433 use Scalar::Util qw/weaken dualvar/;
  13         29  
  13         886  
11 13     13   2591 use POSIX qw/LONG_MAX/;
  13         35670  
  13         99  
12 13     13   12946 use Clone qw/clone/;
  13         5084  
  13         808  
13 13     13   105 use Carp::Clan qw[^(DBIx::DataModel::|SQL::Abstract)];
  13         31  
  13         164  
14 13     13   1216 use Try::Tiny qw/try catch/;
  13         47  
  13         915  
15 13     13   96 use Module::Load qw/load/;
  13         25  
  13         108  
16 13     13   944 use mro qw/c3/;
  13         40  
  13         83  
17              
18 13     13   402 use DBIx::DataModel;
  13         37  
  13         105  
19 13     13   86 use DBIx::DataModel::Meta::Utils qw/define_readonly_accessors does/;
  13         33  
  13         748  
20 13     13   93 use namespace::clean;
  13         39  
  13         116  
21              
22             #----------------------------------------------------------------------
23             # internals
24             #----------------------------------------------------------------------
25              
26             use overload
27              
28             # overload the stringification operator so that Devel::StackTrace is happy;
29             # also useful to show the SQL (if in sqlized state)
30             '""' => sub {
31 0     0   0 my $self = shift;
32 0     0   0 my $string = try {my ($sql, @bind) = $self->sql;
33 0         0 __PACKAGE__ . "($sql // " . join(", ", @bind) . ")"; }
34 0   0     0 || overload::StrVal($self);
35             }
36 13     13   6698 ;
  13         30  
  13         105  
37              
38              
39             # sequence of states. Stored as dualvars for both ordering and printing
40             use constant {
41 13         67901 NEW => dualvar(1, "new" ),
42             REFINED => dualvar(2, "refined" ),
43             SQLIZED => dualvar(3, "sqlized" ),
44             PREPARED => dualvar(4, "prepared"),
45             EXECUTED => dualvar(5, "executed"),
46 13     13   1642 };
  13         28  
47              
48              
49              
50             #----------------------------------------------------------------------
51             # PUBLIC METHODS
52             #----------------------------------------------------------------------
53              
54             sub new {
55 170     170 0 522 my ($class, $source, %other_args) = @_;
56              
57             # check $source
58 170 50 33     1400 $source
59             && $source->isa('DBIx::DataModel::Source')
60             or croak "invalid source for DBIx::DataModel::Statement->new()";
61              
62             # build the object
63 170         1014 my $self = bless {status => NEW,
64             args => {},
65             pre_bound_params => {},
66             bound_params => [],
67             source => $source}, $class;
68              
69             # add placeholder_regex
70 170         730 my $prefix = $source->schema->{placeholder_prefix};
71 170         1489 $self->{placeholder_regex} = qr/^\Q$prefix\E(.+)/;
72              
73             # parse remaining args, if any
74 170 100       582 $self->refine(%other_args) if %other_args;
75              
76 170         539 return $self;
77             }
78              
79              
80             # accessors
81             define_readonly_accessors( __PACKAGE__, qw/source status/);
82              
83             # proxy methods
84 461     461 0 1231 sub meta_source {shift->{source}->metadm}
85 1219     1219 0 3285 sub schema {shift->{source}->schema}
86              
87              
88             # back to the original state
89             sub reset {
90 0     0 0 0 my ($self, %other_args) = @_;
91              
92 0         0 my $new = (ref $self)->new($self->{source}, %other_args);
93 0         0 %$self = (%$new);
94              
95 0         0 return $self;
96             }
97              
98              
99             sub arg {
100 157     157 0 350 my ($self, $arg_name) = @_;
101              
102 157   50     411 my $args = $self->{args} || {};
103 157         581 return $args->{$arg_name};
104             }
105              
106              
107              
108             #----------------------------------------------------------------------
109             # PUBLIC METHODS IN RELATION WITH SELECT()
110             #----------------------------------------------------------------------
111              
112              
113             sub sql {
114 13     13 0 37 my ($self) = @_;
115              
116 13 50       33 $self->status >= SQLIZED
117             or croak "can't call sql() when in status ". $self->status;
118              
119 12         61 return wantarray ? ($self->{sql}, @{$self->{bound_params}})
120 13 100       65 : $self->{sql};
121             }
122              
123              
124             sub bind {
125 171     171 0 464 my ($self, @args) = @_;
126              
127             # arguments can be a list, a hashref or an arrayref
128 171 100       549 if (@args == 1) {
    100          
    50          
129 149 50       520 if (does $args[0], 'HASH') {
    0          
130 149         1333 @args = %{$args[0]};
  149         500  
131             }
132             elsif (does $args[0], 'ARRAY') {
133 0         0 my $i = 0; @args = map {($i++, $_)} @{$args[0]};
  0         0  
  0         0  
  0         0  
134             }
135             else {
136 0         0 croak "unexpected arg type to bind()";
137             }
138             }
139             elsif (@args == 3) { # name => value, \%datatype (see L)
140             # transform into ->bind($name => [$value, \%datatype])
141 1         3 @args = ($args[0], [$args[1], $args[2]]);
142             }
143             elsif (@args % 2 == 1) {
144 0         0 croak "odd number of args to bind()";
145             }
146              
147             # do bind (different behaviour according to status)
148 171         445 my %args = @args;
149 171 100       524 if ($self->status < SQLIZED) {
150 26         220 while (my ($k, $v) = each %args) {
151 34         139 $self->{pre_bound_params}{$k} = $v;
152             }
153             }
154             else {
155 145         554 while (my ($k, $v) = each %args) {
156 49 100       162 my $indices = $self->{param_indices}{$k}
157             or next; # silently ignore that binding (named placeholder unused)
158 30         153 $self->{bound_params}[$_] = $v foreach @$indices;
159             }
160             }
161              
162             # THINK : probably we should check here that $args{__schema}, if present,
163             # is the same as $self->schema (same database connection) ... but how
164             # to check for "sameness" on database handles ?
165              
166 171         425 return $self;
167             }
168              
169              
170             sub refine {
171 111     111 0 429 my ($self, %more_args) = @_;
172              
173 111 50       304 $self->status <= REFINED
174             or croak "can't refine() when in status " . $self->status;
175 111         244 $self->{status} = REFINED;
176              
177 111         195 my $args = $self->{args};
178              
179 111         398 while (my ($k, $v) = each %more_args) {
180              
181             SWITCH:
182 177         327 for ($k) {
183              
184             # -where : combine with previous 'where' clauses in same statement
185 177 100       505 /^-where$/ and do {
186 60         143 my $sqla = $self->schema->sql_abstract;
187 60         296 $args->{-where} = $sqla->merge_conditions($args->{-where}, $v);
188 60         2405 last SWITCH;
189             };
190              
191             # -fetch : special select() on primary key
192 117 100       243 /^-fetch$/ and do {
193             # build a -where clause on primary key
194 7 100       24 my $primary_key = ref($v) ? $v : [$v];
195 7         17 my @pk_columns = $self->meta_source->primary_key;
196             @pk_columns
197 7 50       21 or croak "fetch: no primary key in source " . $self->meta_source;
198 7 50       22 @pk_columns == @$primary_key
199             or croak sprintf "fetch from %s: primary key should have %d values",
200             $self->meta_source, scalar(@pk_columns);
201 7 100   7   68 List::MoreUtils::all {defined $_} @$primary_key
  7         25  
202             or croak "fetch from " . $self->meta_source . ": "
203             . "undefined val in primary key";
204              
205 6         23 my %where = ();
206 6         17 @where{@pk_columns} = @$primary_key;
207 6         15 my $sqla = $self->schema->sql_abstract;
208 6         30 $args->{-where} = $sqla->merge_conditions($args->{-where}, \%where);
209              
210             # want a single record as result
211 6         247 $args->{-result_as} = "firstrow";
212              
213 6         29 last SWITCH;
214             };
215              
216             # -columns : store in $self->{args}{-columns}; can restrict previous list
217 110 100       309 /^-columns$/ and do {
218 36 100       164 my @cols = does($v, 'ARRAY') ? @$v : ($v);
219 36 50       452 if (my $old_cols = $args->{-columns}) {
220 0 0 0     0 unless (@$old_cols == 1 && $old_cols->[0] eq '*' ) {
221 0         0 foreach my $col (@cols) {
222 0 0   0   0 any {$_ eq $col} @$old_cols
  0         0  
223             or croak "can't restrict -columns on '$col' (was not in the) "
224             . "previous -columns list";
225             }
226             }
227             }
228 36         82 $args->{-columns} = \@cols;
229 36         143 last SWITCH;
230             };
231              
232              
233             # other args are just stored, they will be used later
234             /^-( order_by | group_by | having | for
235             | union(?:_all)? | intersect | except | minus
236             | result_as | post_SQL | pre_exec | post_exec | post_bless
237             | limit | offset | page_size | page_index
238             | column_types | prepare_attrs | dbi_prepare_method
239             | _left_cols | where_on | join_with_USING
240             )$/x
241 74 50       413 and do {$args->{$k} = $v; last SWITCH};
  74         163  
  74         277  
242              
243             # TODO : this hard-coded list of args should be more abstract
244              
245             # otherwise
246 0         0 croak "invalid arg : $k";
247              
248             } # end SWITCH
249             } # end while
250              
251 110         270 return $self;
252             }
253              
254              
255              
256              
257             sub sqlize {
258 142     142 0 305 my ($self, @args) = @_;
259              
260 142 50       332 $self->status < SQLIZED
261             or croak "can't sqlize() when in status ". $self->status;
262              
263             # merge new args into $self->{args}
264 142 50       327 $self->refine(@args) if @args;
265              
266             # shortcuts
267 142         296 my $args = $self->{args};
268 142         293 my $meta_source = $self->meta_source;
269 142         281 my $source_where = $meta_source->{where};
270 142         289 my $sql_abstract = $self->schema->sql_abstract;
271 142   100     484 my $result_as = $args->{-result_as} || "";
272              
273             # build arguments for SQL::Abstract::More
274 142 100       435 $self->refine(-where => $source_where) if $source_where;
275 142         564 my @args_to_copy = qw/-columns -where
276             -union -union_all -intersect -except -minus
277             -order_by -group_by -having
278             -limit -offset -page_size -page_index/;
279 142         400 my %sqla_args = (-from => clone($self->source->db_from),
280             -want_details => 1);
281 142   66     1898 defined $args->{$_} and $sqla_args{$_} = $args->{$_} for @args_to_copy;
282 142   66     785 $sqla_args{-columns} ||= $meta_source->default_columns;
283 142 100 50     507 $sqla_args{-limit} ||= 1
      100        
284             if $result_as eq 'firstrow' && $self->schema->autolimit_firstrow;
285              
286             # "-for" (e.g. "update", "read only")
287 142 100       441 if ($result_as ne 'subquery') {
288 139 100       442 if ($args->{-for}) {
    50          
289 2         7 $sqla_args{-for} = $args->{-for};
290             }
291             elsif (!exists $args->{-for}) {
292 137         309 $sqla_args{-for} = $self->schema->select_implicitly_for;
293             }
294             }
295              
296             # "where_on" : conditions to be added in joins
297 142 100       498 if (my $where_on = $args->{-where_on}) {
298             # check proper usage
299 3 100       11 does $sqla_args{-from}, 'ARRAY'
300             or croak "datasource for '-where_on' was not a join";
301              
302             # retrieve components of the join and check again for proper usage
303 2         21 my ($join_op, $first_table, @other_join_args) = @{$sqla_args{-from}};
  2         6  
304 2 50       8 $join_op eq '-join'
305             or croak "datasource for '-where_on' was not a join";
306              
307             # reverse index (table_name => $join_hash)
308 2         7 my %by_dest_table = reverse @other_join_args;
309              
310             # insert additional conditions into appropriate places
311 2         10 while (my ($table, $additional_cond) = each %$where_on) {
312 4 50       9 my $join_cond = $by_dest_table{$table}
313             or croak "-where_on => {'$table' => ..}: this table is not in the join";
314             $join_cond->{condition}
315             = $sql_abstract->merge_conditions($join_cond->{condition},
316 4         13 $additional_cond);
317 4         130 delete $join_cond->{using};
318             }
319              
320             # TODO: should be able to use paths and aliases as keys, instead of
321             # database table names.
322             # TOCHECK: is this stuff still compatible with the bind() method ?
323             }
324              
325             # adjust join conditions for ON clause or for USING clause
326 141 100       455 if (does $sqla_args{-from}, 'ARRAY') {
327 43 50       469 $sqla_args{-from}[0] eq '-join'
328             or croak "datasource is an arrayref but does not start with -join";
329             my $join_with_USING
330             = exists $args->{-join_with_USING} ? $args->{-join_with_USING}
331 43 100       147 : $self->schema->{join_with_USING};
332 43         110 for (my $i = 2; $i < @{$sqla_args{-from}}; $i += 2) {
  115         282  
333 72         125 my $join_cond = $sqla_args{-from}[$i];
334 72 100       156 if ($join_with_USING) {
335 4 50       16 delete $join_cond->{condition} if $join_cond->{using};
336             }
337             else {
338 68         164 delete $join_cond->{using};
339             }
340             }
341             }
342              
343             # generate SQL
344 141         1354 my $sqla_result = $sql_abstract->select(%sqla_args);
345              
346             # maybe post-process the SQL
347 141 50       137427 if ($args->{-post_SQL}) {
348 0         0 ($sqla_result->{sql}, @{$sqla_result->{bind}})
349 0         0 = $args->{-post_SQL}->($sqla_result->{sql}, @{$sqla_result->{bind}});
  0         0  
350             }
351              
352             # keep $sql / @bind / aliases in $self, and set new status
353 141         404 $self->{bound_params} = $sqla_result->{bind};
354 141         898 $self->{$_} = $sqla_result->{$_} for qw/sql aliased_tables aliased_columns/;
355 141         331 $self->{status} = SQLIZED;
356              
357             # analyze placeholders, and replace by pre_bound params if applicable
358 141 50       443 if (my $regex = $self->{placeholder_regex}) {
359 141         333 for (my $i = 0; $i < @{$self->{bound_params}}; $i++) {
  299         741  
360             $self->{bound_params}[$i] =~ $regex
361 158 100       776 and push @{$self->{param_indices}{$1}}, $i;
  30         222  
362             }
363             }
364 141 50       744 $self->bind($self->{pre_bound_params}) if $self->{pre_bound_params};
365              
366             # compute callback to apply to data rows
367 141         293 my $callback = $self->{args}{-post_bless};
368 141         531 weaken(my $weak_self = $self); # weaken to avoid a circular ref in closure
369             $self->{row_callback} = sub {
370 86     86   145 my $row = shift;
371 86         258 $weak_self->bless_from_DB($row);
372 86 50       233 $callback->($row) if $callback;
373 141         674 };
374              
375 141         783 return $self;
376             }
377              
378              
379              
380             sub prepare {
381 135     135 0 313 my ($self, @args) = @_;
382              
383 135         264 my $meta_source = $self->meta_source;
384              
385 135 50 33     503 $self->sqlize(@args) if @args or $self->status < SQLIZED;
386              
387 134 50       383 $self->status == SQLIZED
388             or croak "can't prepare() when in status " . $self->status;
389              
390             # log the statement and bind values
391 134         395 $self->schema->_debug("PREPARE $self->{sql} / @{$self->{bound_params}}");
  134         737  
392              
393             # call the database
394 134 50       333 my $dbh = $self->schema->dbh or croak "Schema has no dbh";
395             my $method = $self->{args}{-dbi_prepare_method}
396 134   33     574 || $self->schema->dbi_prepare_method;
397 134         418 my @prepare_args = ($self->{sql});
398 134 50       350 if (my $prepare_attrs = $self->{args}{-prepare_attrs}) {
399 0         0 push @prepare_args, $prepare_attrs;
400             }
401 134         1052 $self->{sth} = $dbh->$method(@prepare_args);
402              
403             # new status and return
404 134         23846 $self->{status} = PREPARED;
405 134         337 return $self;
406             }
407              
408              
409             sub sth {
410 496     496 0 1002 my ($self) = @_;
411              
412 496 50       1221 $self->prepare if $self->status < PREPARED;
413 496         1801 return $self->{sth};
414             }
415              
416              
417              
418             sub execute {
419 135     135 0 424 my ($self, @bind_args) = @_;
420              
421             # if not prepared yet, prepare it
422 135 100       676 $self->prepare if $self->status < PREPARED;
423              
424             # TODO: DON'T REMEMBER why the line below was here. Keep it around for a while ...
425 134 50       456 push @bind_args, offset => $self->{offset} if $self->{offset};
426              
427 134 100       486 $self->bind(@bind_args) if @bind_args;
428              
429             # shortcuts
430 134         263 my $args = $self->{args};
431 134         332 my $sth = $self->sth;
432              
433             # previous row_count, row_num and reuse_row are no longer valid
434 134         254 delete $self->{reuse_row};
435 134         278 delete $self->{row_count};
436 134         341 $self->{row_num} = $self->offset;
437              
438             # pre_exec callback
439 134 100       370 $args->{-pre_exec}->($sth) if $args->{-pre_exec};
440              
441             # check that all placeholders were properly bound to values
442 134         248 my @unbound;
443 134 100       222 while (my ($k, $indices) = each %{$self->{param_indices} || {}}) {
  164         904  
444 30 50       139 exists $self->{bound_params}[$indices->[0]] or push @unbound, $k;
445             }
446             not @unbound
447 134 50       398 or croak "unbound placeholders (probably a missing foreign key) : "
448             . CORE::join(", ", @unbound);
449              
450             # bind parameters and execute
451 134         317 my $sqla = $self->schema->sql_abstract;
452 134         266 $sqla->bind_params($sth, @{$self->{bound_params}});
  134         661  
453 134         8552 $sth->execute;
454              
455             # post_exec callback
456 134 100       16219 $args->{-post_exec}->($sth) if $args->{-post_exec};
457              
458 134         323 $self->{status} = EXECUTED;
459 134         350 return $self;
460             }
461              
462              
463              
464              
465              
466             my %cache_result_class;
467              
468             sub select {
469 143     143 0 257 my $self = shift;
470              
471 143 100       620 $self->refine(@_) if @_;
472              
473 141   100     442 my $arg_result_as = $self->arg(-result_as) || 'rows';
474              
475 141 100       438 SWITCH:
476             my ($result_as, @subclass_args)
477             = does($arg_result_as, 'ARRAY') ? @$arg_result_as : ($arg_result_as);
478              
479             # historically,some kinds of results accepted various aliases
480 141         1273 $result_as =~ s/^flat(?:_array|)$/flat_arrayref/;
481 141         241 $result_as =~ s/^arrayref$/rows/;
482 141         285 $result_as =~ s/^fast-statement$/fast_statement/;
483              
484 141         306 for ($result_as) {
485 141 50 100     590 my $subclass = $cache_result_class{$_}
486             ||= $self->_find_result_class($_)
487             or croak "didn't find any ResultAs subclass to implement -result_as => '$_'";
488 140         763 my $result_maker = $subclass->new(@subclass_args);
489 140         478 return $result_maker->get_result($self);
490             }
491             }
492              
493              
494             sub row_count {
495 6     6 0 24795 my ($self) = @_;
496              
497 6 50       25 if (! exists $self->{row_count}) {
498 6 100       21 $self->sqlize if $self->status < SQLIZED;
499 6         20 my ($sql, @bind) = $self->sql;
500              
501             # get syntax used for LIMIT clauses ...
502 6         17 my $sqla = $self->schema->sql_abstract;
503 6         24 my ($limit_sql, undef, undef) = $sqla->limit_offset(0, 0);
504 6         171 $limit_sql =~ s/([()?*])/\\$1/g;
505              
506             # ...and use it to remove the LIMIT clause and associated bind vals, if any
507 6 100       85 if ($limit_sql =~ /ROWNUM/) { # special case for Oracle syntax, complex ...
    100          
508             # see source code of SQL::Abstract::More
509 2         8 $limit_sql =~ s/%s/(.*)/;
510 2 100       37 if ($sql =~ s/^$limit_sql/$1/) {
511 1         3 splice @bind, -2;
512             }
513             }
514             elsif ($sql =~ s[\b$limit_sql][]i) { # regular LIMIT/OFFSET syntaxes
515 3         8 splice @bind, -2;
516             }
517              
518             # decide if the SELECT COUNT should wrap the original SQL in a subquery;
519             # this is needed with clauses like below that change the number of rows
520 6         33 my $should_wrap = $sql =~ /\b(UNION|INTERSECT|MINUS|EXCEPT|DISTINCT)\b/i;
521              
522             # if no wrap required, attempt to directly substitute COUNT(*) for the
523             # column names ...but if it fails, wrap anyway
524 6   66     39 $should_wrap ||= ! ($sql =~ s[^SELECT\b.*?\bFROM\b][SELECT COUNT(*) FROM]i);
525              
526             # wrap SQL if needed, using a subquery alias because it's required for
527             # some DBMS (like PostgreSQL)
528 6 100       28 $should_wrap and $sql = "SELECT COUNT(*) FROM "
529             . $sqla->table_alias("( $sql )", "count_wrapper");
530              
531             # log the statement and bind values
532 6         127 $self->schema->_debug("PREPARE $sql / @bind");
533              
534             # call the database
535 6 50       15 my $dbh = $self->schema->dbh or croak "Schema has no dbh";
536 6         16 my $method = $self->schema->dbi_prepare_method;
537 6         62 my $sth = $dbh->$method($sql);
538 6         910 $sth->execute(@bind);
539 6         720 ($self->{row_count}) = $sth->fetchrow_array;
540 6         236 $sth->finish;
541             }
542              
543 6         205 return $self->{row_count};
544             }
545              
546              
547             sub row_num {
548 1     1 0 4 my ($self) = @_;
549 1         7 return $self->{row_num};
550             }
551              
552             sub next {
553 178     178 0 367 my ($self, $n_rows) = @_;
554              
555 178 100       447 $self->execute if $self->status < EXECUTED;
556              
557 177 50       422 my $sth = $self->sth or croak "absent sth in statement";
558 177 50       475 my $callback = $self->{row_callback} or croak "absent callback in statement";
559              
560 177 100       461 if (not defined $n_rows) { # if user wants a single row
561             # fetch a single record, either into the reusable row, or into a fresh hash
562             my $row = $self->{reuse_row} ? ($sth->fetch ? $self->{reuse_row} : undef)
563 85 100       456 : $sth->fetchrow_hashref;
    100          
564 85 100       5945 if ($row) {
565 51         136 $callback->($row);
566 51         99 $self->{row_num} +=1;
567             }
568 85         297 return $row;
569             }
570             else { # if user wants an arrayref of size $n_rows
571 92 50       235 $n_rows > 0 or croak "->next() : invalid argument, $n_rows";
572 92 50       241 not $self->{reuse_row} or croak "reusable row, cannot retrieve several";
573 92         152 my @rows;
574 92         267 while ($n_rows--) {
575 127 100       460 my $row = $sth->fetchrow_hashref or last;
576 35         3204 push @rows, $row;
577             }
578 92         5491 $callback->($_) foreach @rows;
579 92         191 $self->{row_num} += @rows;
580 92         231 return \@rows;
581             }
582              
583             # NOTE: ->next() returns a $row, while ->next(1) returns an arrayref of 1 row
584             }
585              
586              
587             sub all {
588 93     93 0 245 my ($self) = @_;
589              
590             # just call next() with a huge number
591 93         230 return $self->_next_and_finish(POSIX::LONG_MAX);
592             }
593              
594              
595 0 0   0 0 0 sub page_size { shift->{args}{-page_size} || POSIX::LONG_MAX }
596 0 0   0 0 0 sub page_index { shift->{args}{-page_index} || 1 }
597 134 50   134 0 632 sub offset { shift->{offset} || 0 }
598              
599              
600             sub page_count {
601 0     0 0 0 my ($self) = @_;
602              
603 0 0       0 my $row_count = $self->row_count or return 0;
604 0   0     0 my $page_size = $self->page_size || 1;
605              
606 0         0 return int(($row_count - 1) / $page_size) + 1;
607             }
608              
609              
610             sub page_boundaries {
611 0     0 0 0 my ($self) = @_;
612              
613 0         0 my $first = $self->offset + 1;
614 0         0 my $last = min($self->row_count, $first + $self->page_size - 1);
615 0         0 return ($first, $last);
616             }
617              
618              
619             sub page_rows {
620 0     0 0 0 my ($self) = @_;
621 0         0 return $self->_next_and_finish($self->page_size);
622             }
623              
624              
625             sub bless_from_DB {
626 107     107 0 210 my ($self, $row) = @_;
627              
628             # inject ref to $schema if in multi-schema mode or if temporary
629             # db_schema is set
630 107         244 my $schema = $self->schema;
631             $row->{__schema} = $schema unless $schema->{is_singleton}
632 107 100 66     565 && !$schema->{db_schema};
633              
634             # bless into appropriate class
635 107         252 bless $row, $self->meta_source->class;
636             # apply handlers
637 107 100       460 $self->{from_DB_handlers} or $self->_compute_from_DB_handlers;
638 107         187 while (my ($column_name, $handler)
639 280         1009 = each %{$self->{from_DB_handlers}}) {
640             exists $row->{$column_name}
641 173 100       459 and $handler->($row->{$column_name}, $row, $column_name, 'from_DB');
642             }
643              
644 107         363 return $row;
645             }
646              
647              
648             sub headers {
649 22     22 0 44 my $self = shift;
650              
651 22 50       56 $self->status == EXECUTED
652             or $self->execute(@_);
653              
654 22   50     54 my $hash_key_name = $self->sth->{FetchHashKeyName} || 'NAME';
655 22         59 return @{$self->sth->{$hash_key_name}};
  22         52  
656             }
657              
658              
659             sub finish {
660 129     129 0 218 my $self = shift;
661 129         276 $self->sth->finish;
662             }
663              
664              
665             sub make_fast {
666 11     11 0 34 my ($self) = @_;
667              
668 11 50       42 $self->status == EXECUTED
669             or croak "cannot make_fast() when in state " . $self->status;
670              
671             # create a reusable hash and bind_columns to it (see L)
672 11         22 my %row;
673 11         31 $self->sth->bind_columns(\(@row{$self->headers}));
674 11         1072 $self->{reuse_row} = \%row;
675             }
676              
677              
678             #----------------------------------------------------------------------
679             # PRIVATE METHODS IN RELATION WITH SELECT()
680             #----------------------------------------------------------------------
681              
682             sub _forbid_callbacks {
683 5     5   15 my ($self, $subclass) = @_;
684              
685 5         16 my $callbacks = CORE::join ", ", grep {$self->arg($_)}
  15         28  
686             qw/-pre_exec -post_exec -post_bless/;
687 5 50       27 if ($callbacks) {
688 0         0 $subclass =~ s/^.*:://;
689 0         0 croak "$callbacks incompatible with -result_as=>'$subclass'";
690             }
691             }
692              
693              
694              
695             sub _next_and_finish {
696 111     111   187 my $self = shift;
697 111         273 my $row_or_rows = $self->next( @_ ); # pass original parameters
698 110         341 $self->finish;
699 110         3736 return $row_or_rows;
700             }
701              
702             sub _compute_from_DB_handlers {
703 59     59   119 my ($self) = @_;
704 59         151 my $meta_source = $self->meta_source;
705 59         151 my $meta_schema = $self->schema->metadm;
706 59         290 my %handlers = $meta_source->_consolidate_hash('column_handlers');
707 59         246 my %aliased_tables = $meta_source->aliased_tables;
708              
709             # iterate over aliased_columns
710 59 100       144 while (my ($alias, $column) = each %{$self->{aliased_columns} || {}}) {
  80         431  
711 21         35 my $table_name;
712 21 100       110 $column =~ s{^([^()]+) # supposed table name (without parens)
713             \. # followed by a dot
714             (?=[^()]+$) # followed by supposed col name (without parens)
715             }{}x
716             and $table_name = $1;
717 21 100       54 if (!$table_name) {
718 12         37 $handlers{$alias} = $handlers{$column};
719             }
720             else {
721 9   66     45 $table_name = $aliased_tables{$table_name} || $table_name;
722              
723             my $table = $meta_schema->table($table_name)
724 16     16   57 || (firstval {($_->{db_name} || '') eq $table_name}
725             ($meta_source, $meta_source->ancestors))
726 0     0   0 || (firstval {uc($_->{db_name} || '') eq uc($table_name)}
727 9 50 33     29 ($meta_source, $meta_source->ancestors))
728             or croak "unknown table name: $table_name";
729              
730 9         35 $handlers{$alias} = $table->{column_handlers}->{$column};
731             }
732             }
733              
734             # handlers may be overridden from args{-column_types}
735 59 100       214 if (my $col_types = $self->{args}{-column_types}) {
736 1         6 while (my ($type_name, $columns) = each %$col_types) {
737 1 50       3 $columns = [$columns] unless does $columns, 'ARRAY';
738 1 50       13 my $type = $self->schema->metadm->type($type_name)
739             or croak "no such column type: $type_name";
740 1         7 $handlers{$_} = $type->{handlers} foreach @$columns;
741             }
742             }
743              
744             # just keep the "from_DB" handlers
745 59         131 my $from_DB_handlers = {};
746 59         213 while (my ($column, $col_handlers) = each %handlers) {
747 153 100       432 my $from_DB_handler = $col_handlers->{from_DB} or next;
748 107         295 $from_DB_handlers->{$column} = $from_DB_handler;
749             }
750 59         179 $self->{from_DB_handlers} = $from_DB_handlers;
751              
752 59         159 return $self;
753             }
754              
755             sub _find_result_class {
756 35     35   72 my $self = shift;
757 35         142 my $name = ucfirst shift;
758 35         89 my $schema = $self->schema;
759 35   33     131 my $schema_class = ref $schema || $schema;
760              
761             # try to find subclass $name within namespace of schema or ancestors
762 35         72 foreach my $namespace (@{$schema->resultAs_classes}) {
  35         177  
763 68         222 my $class = "${namespace}::ResultAs::${name}";
764              
765             # see if that class is already loaded (by checking for a 'get_result' method)
766 68         112 my $is_loaded = defined &{$class."::get_result"};
  68         291  
767              
768             # otherwise, try to load the module
769 68     68   3495 $is_loaded ||= try {load $class; 1}
  34         955  
770 68 100 66 34   632 catch {die $_ if $_ !~ /^Can't locate(?! object method)/};
  34         1402  
771              
772 67 100       1120 return $class if $is_loaded; # true : class is found, exit loop
773             }
774              
775 0           return; # false : class not found
776             }
777              
778              
779              
780             1; # End of DBIx::DataModel::Statement
781              
782             __END__