X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=t%2F100populate.t;h=e7cb830886c6becf2c65f527ed102ce3a257f8ca;hb=2a6dda4b4b591e4da531d6c78ff9dc9e359d5fd9;hp=8bb0fdc6671e1a21e7e78adcb9bd6c26b8fa5484;hpb=84f7e8a1c6303091d753572648e37d3bd7270181;p=dbsrgits%2FDBIx-Class.git diff --git a/t/100populate.t b/t/100populate.t index 8bb0fdc..e7cb830 100644 --- a/t/100populate.t +++ b/t/100populate.t @@ -3,9 +3,14 @@ use warnings; use Test::More; use Test::Exception; +use Test::Warn; use lib qw(t/lib); use DBICTest; +use DBIx::Class::_Util 'sigwarn_silencer'; use Path::Class::File (); +use Math::BigInt; +use List::Util qw/shuffle/; +use Storable qw/nfreeze dclone/; my $schema = DBICTest->init_schema(); @@ -18,10 +23,10 @@ my $schema = DBICTest->init_schema(); # [ 10000, "ntn" ], my $start_id = 'populateXaaaaaa'; -my $rows = 10; +my $rows = 10_000; my $offset = 3; -$schema->populate('Artist', [ [ qw/artistid name/ ], map { [ ($_ + $offset) => $start_id++ ] } ( 1 .. $rows ) ] ); +$schema->populate('Artist', [ [ qw/artistid name/ ], map { [ ($_ + $offset) => $start_id++ ] } shuffle ( 1 .. $rows ) ] ); is ( $schema->resultset ('Artist')->search ({ name => { -like => 'populateX%' } })->count, $rows, @@ -44,7 +49,7 @@ throws_ok ( sub { } } ('Huey', 'Dewey', $ex_title, 'Louie') ]) -}, qr/columns .+ are not unique for populate slice.+$ex_title/ms, 'Readable exception thrown for failed populate'); +}, qr/\Qexecute_for_fetch() aborted with '\E.+ at populate slice.+$ex_title/ms, 'Readable exception thrown for failed populate'); ## make sure populate honors fields/orders in list context ## schema order @@ -83,8 +88,17 @@ is($link4->id, 4, 'Link 4 id'); is($link4->url, undef, 'Link 4 url'); is($link4->title, 'dtitle', 'Link 4 title'); +## variable size dataset +@links = $schema->populate('Link', [ +[ qw/id title url/ ], +[ 41 ], +[ 42, undef, 'url42' ], +]); +is(scalar @links, 2); +is($links[0]->url, undef); +is($links[1]->url, 'url42'); -## make sure populate -> insert_bulk honors fields/orders in void context +## make sure populate -> _insert_bulk honors fields/orders in void context ## schema order $schema->populate('Link', [ [ qw/id url title/ ], @@ -115,12 +129,69 @@ is($link7->id, 7, 'Link 7 id'); is($link7->url, undef, 'Link 7 url'); is($link7->title, 'gtitle', 'Link 7 title'); +## variable size dataset in void ctx +$schema->populate('Link', [ +[ qw/id title url/ ], +[ 71 ], +[ 72, undef, 'url72' ], +]); +@links = $schema->resultset('Link')->search({ id => [71, 72]}, { order_by => 'id' })->all; +is(scalar @links, 2); +is($links[0]->url, undef); +is($links[1]->url, 'url72'); + +## variable size dataset in void ctx, hash version +$schema->populate('Link', [ + { id => 73 }, + { id => 74, title => 't74' }, + { id => 75, url => 'u75' }, +]); +@links = $schema->resultset('Link')->search({ id => [73..75]}, { order_by => 'id' })->all; +is(scalar @links, 3); +is($links[0]->url, undef); +is($links[0]->title, undef); +is($links[1]->url, undef); +is($links[1]->title, 't74'); +is($links[2]->url, 'u75'); +is($links[2]->title, undef); + +## Make sure the void ctx trace is sane +{ + for ( + [ + [ qw/id title url/ ], + [ 81 ], + [ 82, 't82' ], + [ 83, undef, 'url83' ], + ], + [ + { id => 91 }, + { id => 92, title => 't92' }, + { id => 93, url => 'url93' }, + ] + ) { + $schema->is_executed_sql_bind( + sub { + $schema->populate('Link', $_); + }, + [ + [ 'BEGIN' ], + [ + 'INSERT INTO link( id, title, url ) VALUES( ?, ?, ? )', + "__BULK_INSERT__" + ], + [ 'COMMIT' ], + ] + ); + } +} + # populate with literals { my $rs = $schema->resultset('Link'); $rs->delete; - # test _execute_array_empty (insert_bulk with all literal sql) + # test populate with all literal sql (no binds) $rs->populate([ (+{ @@ -153,9 +224,63 @@ is($link7->title, 'gtitle', 'Link 7 title'); $rs->delete; } +# populate with literal+bind +{ + my $rs = $schema->resultset('Link'); + $rs->delete; + + # test populate with all literal/bind sql + $rs->populate([ + (+{ + url => \['?', [ {} => 'cpan.org' ] ], + title => \['?', [ {} => "The 'best of' cpan" ] ], + }) x 5 + ]); + + is((grep { + $_->url eq 'cpan.org' && + $_->title eq "The 'best of' cpan", + } $rs->all), 5, 'populate with all literal/bind'); + + $rs->delete; + + # test populate with mix literal and literal/bind + $rs->populate([ + (+{ + url => \"'cpan.org'", + title => \['?', [ {} => "The 'best of' cpan" ] ], + }) x 5 + ]); + + is((grep { + $_->url eq 'cpan.org' && + $_->title eq "The 'best of' cpan", + } $rs->all), 5, 'populate with all literal/bind SQL'); + + $rs->delete; + + # test mixed binds with literal sql/bind + + $rs->populate([ map { +{ + url => \[ '? || ?', [ {} => 'cpan.org_' ], $_ ], + title => "The 'best of' cpan", + } } (1 .. 5) ]); + + for (1 .. 5) { + ok($rs->find({ url => "cpan.org_$_" }), "Row $_ correctly created with dynamic literal/bind populate" ); + } + + $rs->delete; +} + my $rs = $schema->resultset('Artist'); $rs->delete; throws_ok { + # this warning is correct, but we are not testing it here + # what we are after is the correct exception when an int + # fails to coerce into a sqlite rownum + local $SIG{__WARN__} = sigwarn_silencer( qr/datatype mismatch.+ foo as integer/ ); + $rs->populate([ { artistid => 1, @@ -170,7 +295,7 @@ throws_ok { name => 'foo3', }, ]); -} qr/slice/, 'bad slice'; +} qr/\Qexecute_for_fetch() aborted with 'datatype mismatch\E\b/, 'bad slice fails PK insert'; is($rs->count, 0, 'populate is atomic'); @@ -188,7 +313,7 @@ throws_ok { name => \"'foo'", } ]); -} qr/bind expected/, 'literal sql where bind expected throws'; +} qr/Literal SQL found where a plain bind value is expected/, 'literal sql where bind expected throws'; # ... and vice-versa. @@ -203,7 +328,7 @@ throws_ok { name => \"'foo'", } ]); -} qr/literal SQL expected/i, 'bind where literal sql expected throws'; +} qr/\QIncorrect value (expecting SCALAR-ref/, 'bind where literal sql expected throws'; throws_ok { $rs->populate([ @@ -216,86 +341,181 @@ throws_ok { name => \"'bar'", } ]); -} qr/inconsistent/, 'literal sql must be the same in all slices'; - -# the stringification has nothing to do with the artist name -# this is solely for testing consistency -my $fn = Path::Class::File->new ('somedir/somefilename.tmp'); -my $fn2 = Path::Class::File->new ('somedir/someotherfilename.tmp'); +} qr/Inconsistent literal SQL value/, 'literal sql must be the same in all slices'; -lives_ok { +throws_ok { $rs->populate([ { - name => 'supplied before stringifying object', + artistid => 1, + name => \['?', [ {} => 'foo' ] ], }, { - name => $fn, + artistid => 2, + name => \"'bar'", } ]); -} 'stringifying objects pass through'; +} qr/\QIncorrect value (expecting ARRAYREF-ref/, 'literal where literal+bind expected throws'; -# ... and vice-versa. - -lives_ok { +throws_ok { $rs->populate([ { - name => $fn2, + artistid => 1, + name => \['?', [ { sqlt_datatype => 'foooo' } => 'foo' ] ], }, { - name => 'supplied after stringifying object', - }, + artistid => 2, + name => \['?', [ {} => 'foo' ] ], + } ]); -} 'stringifying objects pass through'; - -for ( - $fn, - $fn2, - 'supplied after stringifying object', - 'supplied before stringifying object' -) { - my $row = $rs->find ({name => $_}); - ok ($row, "Stringification test row '$_' properly inserted"); -} - -$rs->delete; - -# test stringification with ->create rather than Storage::insert_bulk as well +} qr/\QDiffering bind attributes on literal\/bind values not supported for column 'name'/, 'literal+bind with differing attrs throws'; lives_ok { - my @dummy = $rs->populate([ + $rs->populate([ { - name => 'supplied before stringifying object', + artistid => 1, + name => \['?', [ undef, 'foo' ] ], }, { - name => $fn, + artistid => 2, + name => \['?', [ {} => 'bar' ] ], } ]); -} 'stringifying objects pass through'; +} 'literal+bind with semantically identical attrs works after normalization'; + +# test all kinds of population with stringified objects +warnings_like { + local $ENV{DBIC_RT79576_NOWARN}; + + my $rs = $schema->resultset('Artist')->search({}, { columns => [qw(name rank)], order_by => 'artistid' }); + + # the stringification has nothing to do with the artist name + # this is solely for testing consistency + my $fn = Path::Class::File->new ('somedir/somefilename.tmp'); + my $fn2 = Path::Class::File->new ('somedir/someotherfilename.tmp'); + my $rank = Math::BigInt->new(42); + + my $args = { + 'stringifying objects after regular values' => { AoA => [ + [qw( name rank )], + ( map { [ $_, $rank ] } ( + 'supplied before stringifying objects', + 'supplied before stringifying objects 2', + $fn, + $fn2, + )), + ]}, + + 'stringifying objects before regular values' => { AoA => [ + [qw( rank name )], + ( map { [ $rank, $_ ] } ( + $fn, + $fn2, + 'supplied after stringifying objects', + 'supplied after stringifying objects 2', + )), + ]}, + + 'stringifying objects between regular values' => { AoA => [ + [qw( name rank )], + ( map { [ $_, $rank ] } ( + 'supplied before stringifying objects', + $fn, + $fn2, + 'supplied after stringifying objects', + )) + ]}, + + 'stringifying objects around regular values' => { AoA => [ + [qw( rank name )], + ( map { [ $rank, $_ ] } ( + $fn, + 'supplied between stringifying objects', + $fn2, + )) + ]}, + + 'single stringifying object' => { AoA => [ + [qw( rank name )], + [ $rank, $fn ], + ]}, + }; + + # generate the AoH equivalent based on the AoAs above + for my $bag (values %$args) { + my @hdr = @{$bag->{AoA}[0]}; + for my $v ( @{$bag->{AoA}}[1..$#{$bag->{AoA}}] ) { + push @{$bag->{AoH}}, my $h = {}; + @{$h}{@hdr} = @$v; + } + } + + local $Storable::canonical = 1; + my $preimage = nfreeze($args); + + + for my $tst (keys %$args) { + for my $type (qw(AoA AoH)) { + + # test void ctx + $rs->delete; + $rs->populate($args->{$tst}{$type}); + is_deeply( + $rs->all_hri, + $args->{$tst}{AoH}, + "Populate() $tst in void context" + ); + + # test scalar ctx + $rs->delete; + my $dummy = $rs->populate($args->{$tst}{$type}); + is_deeply( + $rs->all_hri, + $args->{$tst}{AoH}, + "Populate() $tst in non-void context" + ); + + # test list ctx + $rs->delete; + my @dummy = $rs->populate($args->{$tst}{$type}); + is_deeply( + $rs->all_hri, + $args->{$tst}{AoH}, + "Populate() $tst in non-void context" + ); + } -# ... and vice-versa. + # test create() as we have everything set up already + $rs->delete; + $rs->create($_) for @{$args->{$tst}{AoH}}; -lives_ok { - my @dummy = $rs->populate([ - { - name => $fn2, - }, - { - name => 'supplied after stringifying object', - }, - ]); -} 'stringifying objects pass through'; - -for ( - $fn, - $fn2, - 'supplied after stringifying object', - 'supplied before stringifying object' -) { - my $row = $rs->find ({name => $_}); - ok ($row, "Stringification test row '$_' properly inserted"); -} + is_deeply( + $rs->all_hri, + $args->{$tst}{AoH}, + "Create() $tst" + ); + } -lives_ok { + ok ( + ($preimage eq nfreeze($args)), + 'Arguments fed to populate()/create() unchanged' + ); + + $rs->delete; +} [ + # warning to be removed around Apr 1st 2015 + # smokers start failing a month before that + ( + ( DBICTest::RunMode->is_author and ( time() > 1427846400 ) ) + or + ( DBICTest::RunMode->is_smoker and ( time() > 1425168000 ) ) + ) + ? () + # one unique for populate() and create() each + : (qr/\QPOSSIBLE *PAST* DATA CORRUPTION detected \E.+\QTrigger condition encountered at @{[ __FILE__ ]} line\E \d/) x 4 +], 'Data integrity warnings as planned'; + +$schema->is_executed_sql_bind( + sub { $schema->resultset('TwoKeys')->populate([{ artist => 1, cd => 5, @@ -313,6 +533,31 @@ lives_ok { autopilot => 'b', }] }]) -} 'multicol-PK has_many populate works'; + }, + [ + [ 'BEGIN' ], + [ 'INSERT INTO twokeys ( artist, cd) + VALUES ( ?, ? )', + '__BULK_INSERT__' + ], + [ 'INSERT INTO fourkeys_to_twokeys ( autopilot, f_bar, f_foo, f_goodbye, f_hello, t_artist, t_cd) + VALUES ( + ?, ?, ?, ?, ?, + ( SELECT me.artist FROM twokeys me WHERE artist = ? AND cd = ? ), + ( SELECT me.cd FROM twokeys me WHERE artist = ? AND cd = ? ) + ) + ', + '__BULK_INSERT__' + ], + [ 'COMMIT' ], + ], + 'multicol-PK has_many populate expected trace' +); + +lives_ok ( sub { + $schema->populate('CD', [ + {cdid => 10001, artist => $artist->id, title => 'Pretty Much Empty', year => 2011, tracks => []}, + ]) +}, 'empty has_many relationship accepted by populate'); done_testing;