package Fey::ORM::Test::Iterator; use strict; use warnings; use Fey::SQL; use Fey::Object::Iterator::FromSelect; use Fey::ORM::Test; use Test::Fatal; use Test::More 0.88; Fey::ORM::Test::require_sqlite(); sub run_shared_tests { my $class = shift; local $Test::Builder::Level = $Test::Builder::Level + 1; Fey::ORM::Test::insert_user_data(); Fey::ORM::Test::insert_message_data(); Fey::ORM::Test::define_basic_classes(); my $schema = Fey::ORM::Test->schema(); my $dbh = Fey::Test::SQLite->dbh(); { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'User', dbh => $dbh, select => $sql, ); is( $iterator->index(), 0, 'index() is 0 before any data has been fetched' ); my $user = $iterator->next(); isa_ok( $user, 'User' ); is( $iterator->index(), 1, 'index() is 1 after first row has been fetched' ); is( $user->user_id(), 1, 'user_id = 1' ); is( $user->username(), 'autarch', 'username = autarch' ); is( $user->email(), 'autarch@example.com', 'email = autarch@example.com' ); $user = $iterator->next(); is( $iterator->index(), 2, 'index() is 2 after second row has been fetched' ); is( $user->user_id(), 42, 'user_id = 42' ); is( $user->username(), 'bubba', 'username = bubba' ); is( $user->email(), 'bubba@example.com', 'email = bubba@example.com' ); $user = $iterator->next(); is( $iterator->index(), 2, 'index() is 2 after attempt to fetch another row' ); is( $user, undef, '$user is undef when there are no more objects to fetch' ); $iterator->reset(); $user = $iterator->next(); is( $iterator->index(), 1, 'index() is 1 after reset and first row has been fetched' ); is( $user->user_id(), 1, 'user_id = 1' ); is( $user->username(), 'autarch', 'username = autarch' ); is( $user->email(), 'autarch@example.com', 'email = autarch@example.com' ); } { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'User', dbh => $dbh, select => $sql, ); # Makes sure that ->all resets first $iterator->next(); my @users = $iterator->all(); is_deeply( [ sort map { $_->user_id() } @users ], [ 1, 42 ], 'all() returns expected result' ); $iterator->reset(); my %user = $iterator->next_as_hash(); is( ( scalar keys %user ), 1, 'next_as_hash() returns hash with one key' ); is( $user{User}->user_id(), 1, 'found expected user via next_as_hash()' ); # Makes sure that ->all resets first $iterator->next(); my @results = $iterator->all_as_hashes(); is_deeply( [ map { [ keys %{$_} ] } @results ], [ ['User'], ['User'] ], 'all_as_hashes returns arrayref of hashes with expected keys' ); is( $results[0]{User}->user_id(), 1, 'found expected first user in result' ); is( $results[1]{User}->user_id(), 42, 'found expected second user in result' ); $iterator->reset(); $iterator->next(); @users = $iterator->remaining(); is_deeply( [ sort map { $_->user_id() } @users ], [42], 'remaining() returns expected result' ); $iterator->reset(); $iterator->next(); @results = $iterator->remaining_as_hashes(); is_deeply( [ map { [ keys %{$_} ] } @results ], [ ['User'] ], 'remaining_as_hashes returns arrayref of hashes with expected keys' ); } { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') ) ->where( $schema->table('User')->column('user_id'), 'IN', 1, 42 ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'User', dbh => $dbh, select => $sql, ); my $user = $iterator->next(); is( $user->user_id(), 1, 'first user_id with bind params in sql object' ); $user = $iterator->next(); is( $user->user_id(), 42, 'second user_id with bind params in sql object' ); } { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') )->where( $schema->table('User')->column('user_id'), 'IN', ( Fey::Placeholder->new() ) x 2 )->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'User', dbh => $dbh, select => $sql, bind_params => [ 1, 42 ], ); my $user = $iterator->next(); is( $user->user_id(), 1, 'first user_id with explicit bind params' ); $user = $iterator->next(); is( $user->user_id(), 42, 'second user_id with explicit bind params' ); } { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username )), $schema->table('Message')->columns(qw( message_id message )), )->from( $schema->tables( 'User', 'Message' ) )->order_by( $schema->table('User')->column('user_id'), $schema->table('Message')->column('message_id'), ); my $iterator = _make_iterator( $class, classes => [ 'User', 'Message' ], dbh => $dbh, select => $sql, ); my ( $user, $message ) = $iterator->next(); is( $user->user_id(), 1, 'first user id is 1' ); is( $message->message_id(), 1, 'first message id is 1' ); $user = $iterator->next(); # testing next() in scalar context isa_ok( $user, 'User' ); $iterator->reset(); is_deeply( [ map { [ $_->[0]->user_id(), $_->[1]->message_id() ] } $iterator->all() ], [ [ 1, 1 ], [ 1, 2 ], [ 42, 10 ], [ 42, 99 ], ], 'all() returns expected set of objects' ); $iterator->reset(); my %result = $iterator->next_as_hash(); is( $result{User}->user_id(), 1, 'first user id is 1' ); is( $result{Message}->message_id(), 1, 'first message id is 1' ); $iterator->reset(); is_deeply( [ map { [ $_->{User}->user_id(), $_->{Message}->message_id() ] } $iterator->all_as_hashes() ], [ [ 1, 1 ], [ 1, 2 ], [ 42, 10 ], [ 42, 99 ], ], 'all_as_hashes() returns expected set of objects' ); } { # This simulates an OUTER JOIN where Message could be NULL my $sql = Fey::SQL->new_select->select( Fey::Literal::Null->new(), $schema->table('User')->columns(qw( user_id username )), )->from( $schema->tables('User') ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => [ 'Message', 'User' ], dbh => $dbh, select => $sql, attribute_map => { 0 => { class => 'Message', attribute => 'message_id', }, 1 => { class => 'User', attribute => 'user_id', }, 2 => { class => 'User', attribute => 'username', }, }, ); my ( $message, $user ) = $iterator->next(); is( $message, undef, 'message object is undefined' ); ok( $user, 'user object is defined' ); } { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'FakeUser', dbh => $dbh, select => $sql, attribute_map => { 0 => { class => 'FakeUser', attribute => 'user_id', }, 1 => { class => 'FakeUser', attribute => 'username', }, 2 => { class => 'FakeUser', attribute => 'email', }, }, ); my $user = $iterator->next(); isa_ok( $user, 'FakeUser' ); is( $user->user_id(), 1, 'user_id = 1' ); is( $user->username(), 'autarch', 'username = autarch' ); is( $user->email(), 'autarch@example.com', 'email = autarch@example.com' ); like( exception { $iterator->next_as_hash() }, qr/\QCannot make a hash unless all classes have a Table() method/, 'cannot call next_as_hash when iterating with FakeUser class' ); like( exception { $iterator->remaining_as_hashes() }, qr/\QCannot make a hash unless all classes have a Table() method/, 'cannot call remaining_as_hashes when iterating with FakeUser class' ); like( exception { $iterator->all_as_hashes() }, qr/\QCannot make a hash unless all classes have a Table() method/, 'cannot call all_as_hashes when iterating with FakeUser class' ); } if ( $class->can('raw_row') ) { my $sql = Fey::SQL->new_select->select( $schema->table('User')->columns(qw( user_id username email )) ) ->from( $schema->table('User') ) ->order_by( $schema->table('User')->column('user_id') ); my $iterator = _make_iterator( $class, classes => 'User', dbh => $dbh, select => $sql, ); $iterator->next(); is_deeply( $iterator->raw_row(), [ 1, 'autarch', 'autarch@example.com' ], 'raw_row returns expected data' ); $iterator->next(); is_deeply( $iterator->raw_row(), [ 42, 'bubba', 'bubba@example.com' ], 'raw_row returns expected data' ); $iterator->next(); is( $iterator->raw_row(), undef, 'raw_row returns undef when iterator is exhausted' ); } } sub _make_iterator { my $class = shift; my %p = @_; if ( $class eq 'Fey::Object::Iterator::FromArray' ) { my $from_select = Fey::Object::Iterator::FromSelect->new(%p); return $class->new( classes => $p{classes}, objects => [ $from_select->all() ], ); } else { return $class->new(%p); } } { package FakeUser; use Moose; has [qw( user_id username email )] => ( is => 'ro' ); no Moose; } 1;