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