1package Alzabo::Runtime::JoinCursor; 2 3use strict; 4use vars qw($VERSION); 5 6use Alzabo::Exceptions; 7use Alzabo::Runtime; 8 9use Params::Validate qw( :all ); 10Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } ); 11 12use base qw( Alzabo::Runtime::Cursor ); 13 14$VERSION = 2.0; 15 16use constant NEW_SPEC => { statement => { isa => 'Alzabo::DriverStatement' }, 17 tables => { type => ARRAYREF }, 18 }; 19 20sub new 21{ 22 my $proto = shift; 23 my $class = ref $proto || $proto; 24 25 my %p = validate( @_, NEW_SPEC ); 26 27 my $self = bless { %p, 28 count => 0, 29 }, $class; 30 31 return $self; 32} 33 34sub next 35{ 36 my $self = shift; 37 38 my @rows; 39 40 my @data = $self->{statement}->next; 41 42 return unless @data; 43 44 my $i = 0; 45 foreach my $t ( @{ $self->{tables} } ) 46 { 47 48 my %pk; 49 my $def = 0; 50 foreach my $c ( $t->primary_key ) 51 { 52 $pk{ $c->name } = $data[$i]; 53 54 $def = 1 if defined $data[$i]; 55 56 $i++; 57 } 58 59 unless ($def) 60 { 61 push @rows, undef; 62 63 my @pre; 64 if ( @pre = $t->prefetch ) 65 { 66 $i += @pre; 67 } 68 69 next; 70 } 71 72 my %prefetch; 73 { 74 my @pre; 75 if ( @pre = $t->prefetch ) 76 { 77 @prefetch{@pre} = @data[ $i .. ($i + $#pre) ]; 78 $i += @pre; 79 } 80 } 81 82 my $row = $t->row_by_pk( pk => \%pk, 83 prefetch => \%prefetch, 84 @_, 85 ); 86 87 push @rows, $row; 88 } 89 90 $self->{count}++; 91 92 return @rows; 93} 94 95sub all_rows 96{ 97 my $self = shift; 98 99 my @all; 100 while ( my @rows = $self->next ) 101 { 102 push @all, [@rows]; 103 } 104 105 $self->{count} = scalar @all; 106 107 return @all; 108} 109 1101; 111 112__END__ 113 114=head1 NAME 115 116Alzabo::Runtime::JoinCursor - Cursor that returns arrays of C<Alzabo::Runtime::Row> objects 117 118=head1 SYNOPSIS 119 120 use Alzabo::Runtime::JoinCursor; 121 122 my $cursor = $schema->join( tables => [ $foo, $bar ], 123 where => [ $foo->column('foo_id'), '=', 1 ] ); 124 125 while ( my @rows = $cursor->next ) 126 { 127 print $rows[0]->select('foo'), "\n"; 128 print $rows[1]->select('bar'), "\n"; 129 } 130 131=head1 DESCRIPTION 132 133Objects in this class are used to return arrays of 134Alzabo::Runtime::Row objects when requested. The cursor does not 135preload objects but rather creates them on demand, which is much more 136efficient. For more details on the rational please see L<the 137RATIONALE FOR CURSORS section in 138Alzabo::Design|Alzabo::Design/RATIONALE FOR CURSORS>. 139 140=head1 INHERITS FROM 141 142L<C<Alzabo::Runtime::Cursor>|Alzabo::Runtime::Cursor> 143 144=head1 METHODS 145 146=head2 next 147 148Returns the next array of 149L<C<Alzabo::Runtime::Row>|Alzabo::Runtime::Row> objects or an empty 150list if no more are available. 151 152If an individual row could not be fetched, then the array may contain 153some C<undef> values. For outer joins, this is normal behavior, but 154for regular joins, this probably indicates a data error. 155 156=head2 all_rows 157 158This method fetches all the rows available from the current point 159onwards. This means that if there are five set of rows that will be 160returned when the object is created and you call C<next()> twice, 161calling C<all_rows()> after it will only return three sets. 162 163The return value is an array of array references. Each of these 164references represents a single set of rows as they would be returned 165from the C<next> method. 166 167=head2 reset 168 169Resets the cursor so that the next L<C<next()>|next> call will return 170the first row of the set. 171 172=head2 count 173 174Returns the number of rowsets returned by the cursor so far. 175 176=head2 next_as_hash 177 178Returns the next rows in a hash, where the hash keys are the table 179names and the hash values are the row object. If a table has been 180included in the join via an outer join, then it is only included in 181the hash if there is a row for that table. 182 183=head1 AUTHOR 184 185Dave Rolsky, <autarch@urth.org> 186 187=cut 188