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   5921 use warnings;
  13         28  
  13         374  
7 13     13   56 use strict;
  13         33  
  13         293  
8 13     13   78 use List::Util qw/min/;
  13         21  
  13         950  
9 13     13   75 use List::MoreUtils qw/firstval any/;
  13         31  
  13         142  
10 13     13   8721 use Scalar::Util qw/weaken dualvar/;
  13         37  
  13         745  
11 13     13   2211 use POSIX qw/LONG_MAX/;
  13         30242  
  13         77  
12 13     13   10822 use Clone qw/clone/;
  13         5398  
  13         626  
13 13     13   86 use Carp::Clan qw[^(DBIx::DataModel::|SQL::Abstract)];
  13         21  
  13         150  
14 13     13   1073 use Try::Tiny qw/try catch/;
  13         23  
  13         719  
15 13     13   80 use Module::Load qw/load/;
  13         37  
  13         87  
16 13     13   758 use mro qw/c3/;
  13         37  
  13         73  
17              
18 13     13   377 use DBIx::DataModel;
  13         33  
  13         94  
19 13     13   69 use DBIx::DataModel::Meta::Utils qw/define_readonly_accessors does/;
  13         20  
  13         547  
20 13     13   77 use namespace::clean;
  13         21  
  13         72  
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   5918 ;
  13         28  
  13         92  
37              
38              
39             # sequence of states. Stored as dualvars for both ordering and printing
40             use constant {
41 13         55849 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   1341 };
  13         39  
47              
48              
49              
50             #----------------------------------------------------------------------
51             # PUBLIC METHODS
52             #----------------------------------------------------------------------
53              
54             sub new {
55 170     170 0 398 my ($class, $source, %other_args) = @_;
56              
57             # check $source
58 170 50 33     1123 $source
59             && $source->isa('DBIx::DataModel::Source')
60             or croak "invalid source for DBIx::DataModel::Statement->new()";
61              
62             # build the object
63 170         783 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         575 my $prefix = $source->schema->{placeholder_prefix};
71 170         1170 $self->{placeholder_regex} = qr/^\Q$prefix\E(.+)/;
72              
73             # parse remaining args, if any
74 170 100       441 $self->refine(%other_args) if %other_args;
75              
76 170         429 return $self;
77             }
78              
79              
80             # accessors
81             define_readonly_accessors( __PACKAGE__, qw/source status/);
82              
83             # proxy methods
84 461     461 0 1019 sub meta_source {shift->{source}->metadm}
85 1219     1219 0 2580 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 285 my ($self, $arg_name) = @_;
101              
102 157   50     334 my $args = $self->{args} || {};
103 157         414 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 28 my ($self) = @_;
115              
116 13 50       27 $self->status >= SQLIZED
117             or croak "can't call sql() when in status ". $self->status;
118              
119 12         56 return wantarray ? ($self->{sql}, @{$self->{bound_params}})
120 13 100       54 : $self->{sql};
121             }
122              
123              
124             sub bind {
125 171     171 0 373 my ($self, @args) = @_;
126              
127             # arguments can be a list, a hashref or an arrayref
128 171 100       355 if (@args == 1) {
    100          
    50          
129 149 50       393 if (does $args[0], 'HASH') {
    0          
130 149         1050 @args = %{$args[0]};
  149         385  
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         324 my %args = @args;
149 171 100       447 if ($self->status < SQLIZED) {
150 26         162 while (my ($k, $v) = each %args) {
151 34         98 $self->{pre_bound_params}{$k} = $v;
152             }
153             }
154             else {
155 145         411 while (my ($k, $v) = each %args) {
156 49 100       149 my $indices = $self->{param_indices}{$k}
157             or next; # silently ignore that binding (named placeholder unused)
158 30         126 $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         328 return $self;
167             }
168              
169              
170             sub refine {
171 111     111 0 350 my ($self, %more_args) = @_;
172              
173 111 50       272 $self->status <= REFINED
174             or croak "can't refine() when in status " . $self->status;
175 111         210 $self->{status} = REFINED;
176              
177 111         155 my $args = $self->{args};
178              
179 111         299 while (my ($k, $v) = each %more_args) {
180              
181             SWITCH:
182 177         256 for ($k) {
183              
184             # -where : combine with previous 'where' clauses in same statement
185 177 100       388 /^-where$/ and do {
186 60         116 my $sqla = $self->schema->sql_abstract;
187 60         238 $args->{-where} = $sqla->merge_conditions($args->{-where}, $v);
188 60         1924 last SWITCH;
189             };
190              
191             # -fetch : special select() on primary key
192 117 100       223 /^-fetch$/ and do {
193             # build a -where clause on primary key
194 7 100       19 my $primary_key = ref($v) ? $v : [$v];
195 7         15 my @pk_columns = $self->meta_source->primary_key;
196             @pk_columns
197 7 50       15 or croak "fetch: no primary key in source " . $self->meta_source;
198 7 50       17 @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   55 List::MoreUtils::all {defined $_} @$primary_key
  7         20  
202             or croak "fetch from " . $self->meta_source . ": "
203             . "undefined val in primary key";
204              
205 6         19 my %where = ();
206 6         14 @where{@pk_columns} = @$primary_key;
207 6         11 my $sqla = $self->schema->sql_abstract;
208 6         27 $args->{-where} = $sqla->merge_conditions($args->{-where}, \%where);
209              
210             # want a single record as result
211 6         200 $args->{-result_as} = "firstrow";
212              
213 6         33 last SWITCH;
214             };
215              
216             # -columns : store in $self->{args}{-columns}; can restrict previous list
217 110 100       210 /^-columns$/ and do {
218 36 100       100 my @cols = does($v, 'ARRAY') ? @$v : ($v);
219 36 50       327 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         61 $args->{-columns} = \@cols;
229 36         125 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       301 and do {$args->{$k} = $v; last SWITCH};
  74         130  
  74         224  
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         242 return $self;
252             }
253              
254              
255              
256              
257             sub sqlize {
258 142     142 0 221 my ($self, @args) = @_;
259              
260 142 50       235 $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       260 $self->refine(@args) if @args;
265              
266             # shortcuts
267 142         215 my $args = $self->{args};
268 142         205 my $meta_source = $self->meta_source;
269 142         217 my $source_where = $meta_source->{where};
270 142         260 my $sql_abstract = $self->schema->sql_abstract;
271 142   100     396 my $result_as = $args->{-result_as} || "";
272              
273             # build arguments for SQL::Abstract::More
274 142 100       361 $self->refine(-where => $source_where) if $source_where;
275 142         428 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         313 my %sqla_args = (-from => clone($self->source->db_from),
280             -want_details => 1);
281 142   66     1923 defined $args->{$_} and $sqla_args{$_} = $args->{$_} for @args_to_copy;
282 142   66     566 $sqla_args{-columns} ||= $meta_source->default_columns;
283 142 100 50     370 $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       344 if ($result_as ne 'subquery') {
288 139 100       350 if ($args->{-for}) {
    50          
289 2         7 $sqla_args{-for} = $args->{-for};
290             }
291             elsif (!exists $args->{-for}) {
292 137         231 $sqla_args{-for} = $self->schema->select_implicitly_for;
293             }
294             }
295              
296             # "where_on" : conditions to be added in joins
297 142 100       354 if (my $where_on = $args->{-where_on}) {
298             # check proper usage
299 3 100       8 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         15 my ($join_op, $first_table, @other_join_args) = @{$sqla_args{-from}};
  2         6  
304 2 50       5 $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         5 my %by_dest_table = reverse @other_join_args;
309              
310             # insert additional conditions into appropriate places
311 2         8 while (my ($table, $additional_cond) = each %$where_on) {
312 4 50       10 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         9 $additional_cond);
317 4         101 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       360 if (does $sqla_args{-from}, 'ARRAY') {
327 43 50       350 $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       111 : $self->schema->{join_with_USING};
332 43         78 for (my $i = 2; $i < @{$sqla_args{-from}}; $i += 2) {
  115         206  
333 72         95 my $join_cond = $sqla_args{-from}[$i];
334 72 100       95 if ($join_with_USING) {
335 4 50       11 delete $join_cond->{condition} if $join_cond->{using};
336             }
337             else {
338 68         124 delete $join_cond->{using};
339             }
340             }
341             }
342              
343             # generate SQL
344 141         1066 my $sqla_result = $sql_abstract->select(%sqla_args);
345              
346             # maybe post-process the SQL
347 141 50       107934 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         300 $self->{bound_params} = $sqla_result->{bind};
354 141         649 $self->{$_} = $sqla_result->{$_} for qw/sql aliased_tables aliased_columns/;
355 141         250 $self->{status} = SQLIZED;
356              
357             # analyze placeholders, and replace by pre_bound params if applicable
358 141 50       305 if (my $regex = $self->{placeholder_regex}) {
359 141         205 for (my $i = 0; $i < @{$self->{bound_params}}; $i++) {
  298         674  
360             $self->{bound_params}[$i] =~ $regex
361 157 100       575 and push @{$self->{param_indices}{$1}}, $i;
  30         134  
362             }
363             }
364 141 50       585 $self->bind($self->{pre_bound_params}) if $self->{pre_bound_params};
365              
366             # compute callback to apply to data rows
367 141         231 my $callback = $self->{args}{-post_bless};
368 141         410 weaken(my $weak_self = $self); # weaken to avoid a circular ref in closure
369             $self->{row_callback} = sub {
370 86     86   111 my $row = shift;
371 86         190 $weak_self->bless_from_DB($row);
372 86 50       172 $callback->($row) if $callback;
373 141         531 };
374              
375 141         625 return $self;
376             }
377              
378              
379              
380             sub prepare {
381 135     135 0 213 my ($self, @args) = @_;
382              
383 135         225 my $meta_source = $self->meta_source;
384              
385 135 50 33     394 $self->sqlize(@args) if @args or $self->status < SQLIZED;
386              
387 134 50       314 $self->status == SQLIZED
388             or croak "can't prepare() when in status " . $self->status;
389              
390             # log the statement and bind values
391 134         287 $self->schema->_debug("PREPARE $self->{sql} / @{$self->{bound_params}}");
  134         584  
392              
393             # call the database
394 134 50       246 my $dbh = $self->schema->dbh or croak "Schema has no dbh";
395             my $method = $self->{args}{-dbi_prepare_method}
396 134   33     464 || $self->schema->dbi_prepare_method;
397 134         289 my @prepare_args = ($self->{sql});
398 134 50       256 if (my $prepare_attrs = $self->{args}{-prepare_attrs}) {
399 0         0 push @prepare_args, $prepare_attrs;
400             }
401 134         783 $self->{sth} = $dbh->$method(@prepare_args);
402              
403             # new status and return
404 134         17657 $self->{status} = PREPARED;
405 134         281 return $self;
406             }
407              
408              
409             sub sth {
410 496     496 0 763 my ($self) = @_;
411              
412 496 50       986 $self->prepare if $self->status < PREPARED;
413 496         1356 return $self->{sth};
414             }
415              
416              
417              
418             sub execute {
419 135     135 0 292 my ($self, @bind_args) = @_;
420              
421             # if not prepared yet, prepare it
422 135 100       250 $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       331 push @bind_args, offset => $self->{offset} if $self->{offset};
426              
427 134 100       344 $self->bind(@bind_args) if @bind_args;
428              
429             # shortcuts
430 134         226 my $args = $self->{args};
431 134         276 my $sth = $self->sth;
432              
433             # previous row_count, row_num and reuse_row are no longer valid
434 134         192 delete $self->{reuse_row};
435 134         177 delete $self->{row_count};
436 134         241 $self->{row_num} = $self->offset;
437              
438             # pre_exec callback
439 134 100       271 $args->{-pre_exec}->($sth) if $args->{-pre_exec};
440              
441             # check that all placeholders were properly bound to values
442 134         203 my @unbound;
443 134 100       187 while (my ($k, $indices) = each %{$self->{param_indices} || {}}) {
  164         701  
444 30 50       109 exists $self->{bound_params}[$indices->[0]] or push @unbound, $k;
445             }
446             not @unbound
447 134 50       309 or croak "unbound placeholders (probably a missing foreign key) : "
448             . CORE::join(", ", @unbound);
449              
450             # bind parameters and execute
451 134         249 my $sqla = $self->schema->sql_abstract;
452 134         207 $sqla->bind_params($sth, @{$self->{bound_params}});
  134         463  
453 134         6559 $sth->execute;
454              
455             # post_exec callback
456 134 100       12542 $args->{-post_exec}->($sth) if $args->{-post_exec};
457              
458 134         248 $self->{status} = EXECUTED;
459 134         258 return $self;
460             }
461              
462              
463              
464              
465              
466             my %cache_result_class;
467              
468             sub select {
469 143     143 0 212 my $self = shift;
470              
471 143 100       446 $self->refine(@_) if @_;
472              
473 141   100     341 my $arg_result_as = $self->arg(-result_as) || 'rows';
474              
475 141 100       357 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         1063 $result_as =~ s/^flat(?:_array|)$/flat_arrayref/;
481 141         185 $result_as =~ s/^arrayref$/rows/;
482 141         175 $result_as =~ s/^fast-statement$/fast_statement/;
483              
484 141         233 for ($result_as) {
485 141 50 100     464 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         575 my $result_maker = $subclass->new(@subclass_args);
489 140         325 return $result_maker->get_result($self);
490             }
491             }
492              
493              
494             sub row_count {
495 6     6 0 19873 my ($self) = @_;
496              
497 6 50       20 if (! exists $self->{row_count}) {
498 6 100       19 $self->sqlize if $self->status < SQLIZED;
499 6         15 my ($sql, @bind) = $self->sql;
500              
501             # get syntax used for LIMIT clauses ...
502 6         14 my $sqla = $self->schema->sql_abstract;
503 6         21 my ($limit_sql, undef, undef) = $sqla->limit_offset(0, 0);
504 6         113 $limit_sql =~ s/([()?*])/\\$1/g;
505              
506             # ...and use it to remove the LIMIT clause and associated bind vals, if any
507 6 100       80 if ($limit_sql =~ /ROWNUM/) { # special case for Oracle syntax, complex ...
    100          
508             # see source code of SQL::Abstract::More
509 2         35 $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         26 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     33 $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       23 $should_wrap and $sql = "SELECT COUNT(*) FROM "
529             . $sqla->table_alias("( $sql )", "count_wrapper");
530              
531             # log the statement and bind values
532 6         131 $self->schema->_debug("PREPARE $sql / @bind");
533              
534             # call the database
535 6 50       12 my $dbh = $self->schema->dbh or croak "Schema has no dbh";
536 6         12 my $method = $self->schema->dbi_prepare_method;
537 6         31 my $sth = $dbh->$method($sql);
538 6         685 $sth->execute(@bind);
539 6         628 ($self->{row_count}) = $sth->fetchrow_array;
540 6         195 $sth->finish;
541             }
542              
543 6         161 return $self->{row_count};
544             }
545              
546              
547             sub row_num {
548 1     1 0 4 my ($self) = @_;
549 1         5 return $self->{row_num};
550             }
551              
552             sub next {
553 178     178 0 282 my ($self, $n_rows) = @_;
554              
555 178 100       371 $self->execute if $self->status < EXECUTED;
556              
557 177 50       338 my $sth = $self->sth or croak "absent sth in statement";
558 177 50       352 my $callback = $self->{row_callback} or croak "absent callback in statement";
559              
560 177 100       345 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       343 : $sth->fetchrow_hashref;
    100          
564 85 100       4696 if ($row) {
565 51         108 $callback->($row);
566 51         79 $self->{row_num} +=1;
567             }
568 85         220 return $row;
569             }
570             else { # if user wants an arrayref of size $n_rows
571 92 50       180 $n_rows > 0 or croak "->next() : invalid argument, $n_rows";
572 92 50       166 not $self->{reuse_row} or croak "reusable row, cannot retrieve several";
573 92         129 my @rows;
574 92         179 while ($n_rows--) {
575 127 100       349 my $row = $sth->fetchrow_hashref or last;
576 35         2361 push @rows, $row;
577             }
578 92         4201 $callback->($_) foreach @rows;
579 92         151 $self->{row_num} += @rows;
580 92         199 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 176 my ($self) = @_;
589              
590             # just call next() with a huge number
591 93         193 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 468 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 162 my ($self, $row) = @_;
627              
628             # inject ref to $schema if in multi-schema mode or if temporary
629             # db_schema is set
630 107         210 my $schema = $self->schema;
631             $row->{__schema} = $schema unless $schema->{is_singleton}
632 107 100 66     395 && !$schema->{db_schema};
633              
634             # bless into appropriate class
635 107         190 bless $row, $self->meta_source->class;
636             # apply handlers
637 107 100       349 $self->{from_DB_handlers} or $self->_compute_from_DB_handlers;
638 107         131 while (my ($column_name, $handler)
639 280         754 = each %{$self->{from_DB_handlers}}) {
640             exists $row->{$column_name}
641 173 100       363 and $handler->($row->{$column_name}, $row, $column_name, 'from_DB');
642             }
643              
644 107         290 return $row;
645             }
646              
647              
648             sub headers {
649 22     22 0 33 my $self = shift;
650              
651 22 50       50 $self->status == EXECUTED
652             or $self->execute(@_);
653              
654 22   50     44 my $hash_key_name = $self->sth->{FetchHashKeyName} || 'NAME';
655 22         45 return @{$self->sth->{$hash_key_name}};
  22         36  
656             }
657              
658              
659             sub finish {
660 129     129 0 177 my $self = shift;
661 129         215 $self->sth->finish;
662             }
663              
664              
665             sub make_fast {
666 11     11 0 22 my ($self) = @_;
667              
668 11 50       30 $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         18 my %row;
673 11         24 $self->sth->bind_columns(\(@row{$self->headers}));
674 11         798 $self->{reuse_row} = \%row;
675             }
676              
677              
678             #----------------------------------------------------------------------
679             # PRIVATE METHODS IN RELATION WITH SELECT()
680             #----------------------------------------------------------------------
681              
682             sub _forbid_callbacks {
683 5     5   13 my ($self, $subclass) = @_;
684              
685 5         14 my $callbacks = CORE::join ", ", grep {$self->arg($_)}
  15         22  
686             qw/-pre_exec -post_exec -post_bless/;
687 5 50       21 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   143 my $self = shift;
697 111         201 my $row_or_rows = $self->next( @_ ); # pass original parameters
698 110         257 $self->finish;
699 110         3015 return $row_or_rows;
700             }
701              
702             sub _compute_from_DB_handlers {
703 59     59   129 my ($self) = @_;
704 59         94 my $meta_source = $self->meta_source;
705 59         100 my $meta_schema = $self->schema->metadm;
706 59         216 my %handlers = $meta_source->_consolidate_hash('column_handlers');
707 59         543 my %aliased_tables = $meta_source->aliased_tables;
708              
709             # iterate over aliased_columns
710 59 100       106 while (my ($alias, $column) = each %{$self->{aliased_columns} || {}}) {
  80         327  
711 21         29 my $table_name;
712 21 100       84 $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       41 if (!$table_name) {
718 12         28 $handlers{$alias} = $handlers{$column};
719             }
720             else {
721 9   66     27 $table_name = $aliased_tables{$table_name} || $table_name;
722              
723             my $table = $meta_schema->table($table_name)
724 16     16   44 || (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     23 ($meta_source, $meta_source->ancestors))
728             or croak "unknown table name: $table_name";
729              
730 9         26 $handlers{$alias} = $table->{column_handlers}->{$column};
731             }
732             }
733              
734             # handlers may be overridden from args{-column_types}
735 59 100       152 if (my $col_types = $self->{args}{-column_types}) {
736 1         4 while (my ($type_name, $columns) = each %$col_types) {
737 1 50       3 $columns = [$columns] unless does $columns, 'ARRAY';
738 1 50       8 my $type = $self->schema->metadm->type($type_name)
739             or croak "no such column type: $type_name";
740 1         6 $handlers{$_} = $type->{handlers} foreach @$columns;
741             }
742             }
743              
744             # just keep the "from_DB" handlers
745 59         92 my $from_DB_handlers = {};
746 59         157 while (my ($column, $col_handlers) = each %handlers) {
747 153 100       338 my $from_DB_handler = $col_handlers->{from_DB} or next;
748 107         241 $from_DB_handlers->{$column} = $from_DB_handler;
749             }
750 59         93 $self->{from_DB_handlers} = $from_DB_handlers;
751              
752 59         109 return $self;
753             }
754              
755             sub _find_result_class {
756 35     35   61 my $self = shift;
757 35         82 my $name = ucfirst shift;
758 35         63 my $schema = $self->schema;
759 35   33     90 my $schema_class = ref $schema || $schema;
760              
761             # try to find subclass $name within namespace of schema or ancestors
762 35         48 foreach my $namespace (@{$schema->resultAs_classes}) {
  35         114  
763 68         163 my $class = "${namespace}::ResultAs::${name}";
764              
765             # see if that class is already loaded (by checking for a 'get_result' method)
766 68         86 my $is_loaded = defined &{$class."::get_result"};
  68         234  
767              
768             # otherwise, try to load the module
769 68     68   2681 $is_loaded ||= try {load $class; 1}
  34         782  
770 68 100 66 34   484 catch {die $_ if $_ !~ /^Can't locate(?! object method)/};
  34         1058  
771              
772 67 100       898 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__