+use DBIx::Class::Optional::Dependencies -skip_all_without => 'test_rdbms_pg';
+
use strict;
use warnings;
use Test::More;
use Test::Exception;
+use Test::Warn;
+use Sub::Name;
+use Config;
+use DBIx::Class::Optional::Dependencies ();
use lib qw(t/lib);
use DBICTest;
-
+use SQL::Abstract 'is_literal_value';
+use DBIx::Class::_Util 'is_exception';
my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_PG_${_}" } qw/DSN USER PASS/};
-plan skip_all => <<EOM unless $dsn && $user;
-Set \$ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test
-( NOTE: This test drops and creates tables called 'artist', 'cd',
-'timestamp_primary_key_test', 'track', 'casecheck', 'array_test' and
-'sequence_test' as well as following sequences: 'pkid1_seq', 'pkid2_seq' and
-'nonpkid_seq''. as well as following schemas: 'dbic_t_schema',
-'dbic_t_schema_2', 'dbic_t_schema_3', 'dbic_t_schema_4', and 'dbic_t_schema_5')
-EOM
-
### load any test classes that are defined further down in the file via BEGIN blocks
-
our @test_classes; #< array that will be pushed into by test classes defined in this file
DBICTest::Schema->load_classes( map {s/.+:://;$_} @test_classes ) if @test_classes;
-my $schema;
-
-for my $use_insert_returning (0..1) {
- no warnings qw/redefine once/;
- require DBIx::Class::Storage::DBI::Pg;
- local *DBIx::Class::Storage::DBI::Pg::can_insert_returning = sub {
- $use_insert_returning
- };
-
### pre-connect tests (keep each test separate as to make sure rebless() runs)
{
my $s = DBICTest::Schema->connect($dsn, $user, $pass);
-
+ # make sure sqlt_type overrides work (::Storage::DBI::Pg does this)
ok (!$s->storage->_dbh, 'definitely not connected');
+ is ($s->storage->sqlt_type, 'PostgreSQL', 'sqlt_type correct pre-connection');
+ ok (!$s->storage->_dbh, 'still not connected');
+ }
- # Check that datetime_parser returns correctly before we explicitly connect.
- SKIP: {
- eval { require DateTime::Format::Pg };
- skip "DateTime::Format::Pg required", 2 if $@;
+# test LIMIT support
+{
+ my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
+ drop_test_schema($schema);
+ create_test_schema($schema);
+ for (1..6) {
+ $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
+ }
+ my $it = $schema->resultset('Artist')->search( {},
+ { rows => 3,
+ offset => 2,
+ order_by => 'artistid' }
+ );
+ is( $it->count, 3, "LIMIT count ok" ); # ask for 3 rows out of 6 artists
+ is( $it->next->name, "Artist 3", "iterator->next ok" );
+ $it->next;
+ $it->next;
+ $it->next;
+ is( $it->next, undef, "next past end of resultset ok" );
+
+ # Limit with select-lock
+ lives_ok {
+ $schema->txn_do (sub {
+ isa_ok (
+ $schema->resultset('Artist')->find({artistid => 1}, {for => 'update', rows => 1}),
+ 'DBICTest::Schema::Artist',
+ );
+ });
+ } 'Limited FOR UPDATE select works';
+}
- my $store = ref $s->storage;
- is($store, 'DBIx::Class::Storage::DBI', 'Started with generic storage');
+# check if we indeed do support stuff
+my $test_server_supports_insert_returning = do {
- my $parser = $s->storage->datetime_parser;
- is( $parser, 'DateTime::Format::Pg', 'datetime_parser is as expected');
- }
+ my $si = DBICTest::Schema->connect($dsn, $user, $pass)->storage->_server_info;
+ die "Unparseable Pg server version: $si->{dbms_version}\n"
+ unless $si->{normalized_dbms_version};
- ok (!$s->storage->_dbh, 'still not connected');
- }
+ $si->{normalized_dbms_version} < 8.002 ? 0 : 1;
+};
+is (
+ DBICTest::Schema->connect($dsn, $user, $pass)->storage->_use_insert_returning,
+ $test_server_supports_insert_returning,
+ 'insert returning capability guessed correctly'
+);
+
+my $schema;
+for my $use_insert_returning ($test_server_supports_insert_returning
+ ? (0,1)
+ : (0)
+) {
+
+ no warnings qw/once redefine/;
+ my $old_connection = DBICTest::Schema->can('connection');
+ local *DBICTest::Schema::connection = subname 'DBICTest::Schema::connection' => sub {
+ my $s = shift->$old_connection(@_);
+ $s->storage->_use_insert_returning ($use_insert_returning);
+ $s;
+ };
+
+### test capability override
{
my $s = DBICTest::Schema->connect($dsn, $user, $pass);
- # make sure sqlt_type overrides work (::Storage::DBI::Pg does this)
+
ok (!$s->storage->_dbh, 'definitely not connected');
- is ($s->storage->sqlt_type, 'PostgreSQL', 'sqlt_type correct pre-connection');
- ok (!$s->storage->_dbh, 'still not connected');
+
+ ok (
+ ! ($s->storage->_use_insert_returning xor $use_insert_returning),
+ 'insert returning capability set correctly',
+ );
+ ok (!$s->storage->_dbh, 'still not connected (capability override works)');
}
### connect, create postgres-specific test schema
$schema = DBICTest::Schema->connect($dsn, $user, $pass);
+ $schema->storage->ensure_connected;
drop_test_schema($schema);
create_test_schema($schema);
run_apk_tests($schema); #< older set of auto-pk tests
run_extended_apk_tests($schema); #< new extended set of auto-pk tests
+
+######## test the pg-specific syntax from https://rt.cpan.org/Ticket/Display.html?id=99503
+ lives_ok {
+ is(
+ $schema->resultset('Artist')->search({ artistid => { -in => \ '(select 4) union (select 5)' } })->count,
+ 2,
+ 'Two expected artists found on subselect union within IN',
+ );
+ };
+
### type_info tests
my $test_type_info = {
my $type_info = $schema->storage->columns_info_for('dbic_t_schema.artist');
my $artistid_defval = delete $type_info->{artistid}->{default_value};
- like($artistid_defval,
- qr/^nextval\('([^\.]*\.){0,1}artist_artistid_seq'::(?:text|regclass)\)/,
- 'columns_info_for - sequence matches Pg get_autoinc_seq expectations');
- is_deeply($type_info, $test_type_info,
- 'columns_info_for - column data types');
-
+ # The curor info is too radically different from what is in the column_info
+ # call - just punt it (DBD::SQLite tests the codepath plenty enough)
+ unless (DBIx::Class::_ENV_::STRESSTEST_COLUMN_INFO_UNAWARE_STORAGE) {
+ like(
+ $artistid_defval,
+ qr/^nextval\('([^\.]*\.){0,1}artist_artistid_seq'::(?:text|regclass)\)/,
+ 'columns_info_for - sequence matches Pg get_autoinc_seq expectations'
+ );
+ is_deeply($type_info, $test_type_info,
+ 'columns_info_for - column data types');
+ }
####### Array tests
use strict;
use warnings;
- use base 'DBIx::Class::Core';
+ use base 'DBICTest::BaseResult';
__PACKAGE__->table('dbic_t_schema.array_test');
__PACKAGE__->add_columns(qw/id arrayfield/);
SKIP: {
skip "Need DBD::Pg 2.9.2 or newer for array tests", 4 if $DBD::Pg::VERSION < 2.009002;
+ my $arr_rs = $schema->resultset('ArrayTest');
+
lives_ok {
- $schema->resultset('ArrayTest')->create({
+ $arr_rs->create({
arrayfield => [1, 2],
});
} 'inserting arrayref as pg array data';
lives_ok {
- $schema->resultset('ArrayTest')->update({
+ $arr_rs->update({
arrayfield => [3, 4],
});
} 'updating arrayref as pg array data';
- $schema->resultset('ArrayTest')->create({
+ $arr_rs->create({
arrayfield => [5, 6],
});
- my $count;
lives_ok {
- $count = $schema->resultset('ArrayTest')->search({
- arrayfield => \[ '= ?' => [arrayfield => [3, 4]] ], #Todo anything less ugly than this?
- })->count;
- } 'comparing arrayref to pg array data does not blow up';
- is($count, 1, 'comparing arrayref to pg array data gives correct result');
- }
+ $schema->populate('ArrayTest', [
+ [ qw/arrayfield/ ],
+ [ [0,0] ],
+ ]);
+ } 'inserting arrayref using void ctx populate';
+ # Search using arrays
+ lives_ok {
+ is_deeply (
+ $arr_rs->search({ arrayfield => { -value => [3,4] } })->first->arrayfield,
+ [3,4],
+ 'Array value matches'
+ );
+ } 'searching by arrayref';
+
+ lives_ok {
+ is_deeply (
+ $arr_rs->search({ arrayfield => { '=' => { -value => [3,4] }} })->first->arrayfield,
+ [3,4],
+ 'Array value matches explicit equal'
+ );
+ } 'searching by arrayref (explicit equal sign)';
+ lives_ok {
+ is_deeply (
+ $arr_rs->search({ arrayfield => { '>' => { -value => [3,1] }} })->first->arrayfield,
+ [3,4],
+ 'Array value matches greater than'
+ );
+ } 'searching by arrayref (greater than)';
+
+ lives_ok {
+ is (
+ $arr_rs->search({ arrayfield => { '>' => { -value => [3,7] }} })->count,
+ 1,
+ 'Greater than search found [5,6]',
+ );
+ } 'searching by arrayref (greater than)';
+
+ # Find using arrays
+ lives_ok {
+ is_deeply (
+ $arr_rs->find({ arrayfield => { -value => [3,4] } })->arrayfield,
+ [3,4],
+ 'Array value matches implicit equal'
+ );
+ } 'find by arrayref';
+
+ lives_ok {
+ is_deeply (
+ $arr_rs->find({ arrayfield => { '=' => { -value => [3,4] }} })->arrayfield,
+ [3,4],
+ 'Array value matches explicit equal'
+ );
+ } 'find by arrayref (equal)';
+
+ # test inferred condition for creation
+ for my $cond (
+ { -value => [3,4] },
+ \[ '= ?' => [3, 4] ],
+ ) {
+ local $TODO = 'No introspection of complex literal conditions :('
+ if is_literal_value $cond;
+
+
+ my $arr_rs_cond = $arr_rs->search({ arrayfield => $cond });
+
+ my $row = $arr_rs_cond->create({});
+ is_deeply ($row->arrayfield, [3,4], 'Array value taken from $rs condition');
+ $row->discard_changes;
+ is_deeply ($row->arrayfield, [3,4], 'Array value made it to storage');
+ }
+
+ my $arr = [ 1..10 ];
+ # exercise the creation-logic even more (akin to t/100populate.t)
+ for my $insert_value (
+ $arr,
+ { -value => $arr },
+ \[ '?', $arr ],
+ ) {
+ $arr_rs->delete;
+
+ my @objs = (
+ $arr_rs->create({ arrayfield => $insert_value }),
+ $arr_rs->populate([ { arrayfield => $insert_value } ]),
+ $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]),
+ );
+
+ my $loose_obj = $arr_rs->new({ arrayfield => $insert_value });
+
+ unless (is_literal_value $insert_value) {
+ is_deeply( $_->arrayfield, $arr, 'array value preserved during set_columns' )
+ for ($loose_obj, @objs)
+ }
+
+ push @objs, $loose_obj->insert;
+
+ $_->discard_changes for @objs;
+ is_deeply( $_->arrayfield, $arr, 'array value correct after discard_changes' )
+ for (@objs);
+
+ # insert couple more in void ctx
+ $arr_rs->populate([ { arrayfield => $insert_value } ]);
+ $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]);
+
+ # should have a total of 6 now, all pristine
+ my @retrieved_objs = $arr_rs->search({
+ arrayfield => ref $insert_value eq 'ARRAY'
+ ? { -value => $insert_value }
+ : { '=' => $insert_value }
+ })->all;
+ is scalar @retrieved_objs, 6, 'Correct count of inserted rows';
+ is_deeply( $_->arrayfield, $arr, 'array value correct after storage retrieval' )
+ for (@retrieved_objs);
+ }
+ }
########## Case check
is( $uc_name_info->{size}, 3, "Case insensitive matching info for 'uc_name'" );
-
+## Test ResultSet->update
+my $artist = $schema->resultset('Artist')->first;
+my $cds = $artist->cds_unordered->search({
+ year => { '!=' => 2010 }
+}, { prefetch => 'liner_notes' });
+lives_ok { $cds->update({ year => '2010' }) } 'Update on prefetched rs';
## Test SELECT ... FOR UPDATE
-
SKIP: {
- if(eval "require Sys::SigAction" && !$@) {
- Sys::SigAction->import( 'set_sig_handler' );
- }
- else {
- skip "Sys::SigAction is not available", 6;
- }
+ skip "Your system does not support unsafe signals (d_sigaction) - unable to run deadlock test", 1
+ unless eval { $Config{d_sigaction} and require POSIX };
my ($timed_out, $artist2);
$schema2->source("Artist")->name("dbic_t_schema.artist");
$schema->txn_do( sub {
- my $artist = $schema->resultset('Artist')->search(
+ my $rs = $schema->resultset('Artist')->search(
{
artistid => 1
},
$t->{update_lock} ? { for => 'update' } : {}
- )->first;
+ );
+ ok ($rs->count, 'Count works');
+
+ my $artist = $rs->next;
is($artist->artistid, 1, "select returns artistid = 1");
$timed_out = 0;
+
eval {
- my $h = set_sig_handler( 'ALRM', sub { die "DBICTestTimeout" } );
- alarm(2);
+ # can not use %SIG assignment directly - we need sigaction below
+ # localization to a block still works however
+ local $SIG{ALRM};
+
+ POSIX::sigaction( POSIX::SIGALRM() => POSIX::SigAction->new(
+ sub { die "DBICTestTimeout" },
+ ));
+
$artist2 = $schema2->resultset('Artist')->find(1);
$artist2->name('fooey');
+
+ # FIXME - this needs to go away in lieu of a non-retrying runner
+ # ( i.e. after solving RT#47005 )
+ local *DBIx::Class::Storage::DBI::_ping = sub { 1 }, DBIx::Class::_ENV_::OLD_MRO && Class::C3->reinitialize()
+ if DBIx::Class::_Util::modver_gt_or_eq( 'DBD::Pg' => '3.5.0' );
+
+ alarm(1);
$artist2->update;
- alarm(0);
};
- $timed_out = $@ =~ /DBICTestTimeout/;
+
+ alarm(0);
+
+ if (is_exception($@)) {
+ $timed_out = $@ =~ /DBICTestTimeout/
+ or die $@;
+ }
});
$t->{test_sub}->();
######## test non-serial auto-pk
- if ($schema->storage->can_insert_returning) {
+ if ($schema->storage->_use_insert_returning) {
$schema->source('TimestampPrimaryKey')->name('dbic_t_schema.timestamp_primary_key_test');
my $row = $schema->resultset('TimestampPrimaryKey')->create({});
ok $row->id;
} 'with_deferred_fk_checks code survived';
is eval { $schema->resultset('Track')->find(999)->title }, 'deferred FK track',
- 'code in with_deferred_fk_checks worked';
+ 'code in with_deferred_fk_checks worked';
throws_ok {
$schema->resultset('Track')->create({
trackid => 1, cd => 9999, position => 1, title => 'Track1'
});
- } qr/constraint/i, 'with_deferred_fk_checks is off';
+ } qr/violates foreign key constraint/i, 'with_deferred_fk_checks is off outside of TXN';
+
+ # rerun the same under with_deferred_fk_checks
+ # it is expected to fail, hence the eval
+ # but it also should not warn
+ warnings_like {
+ eval {
+ $schema->storage->with_deferred_fk_checks(sub {
+ $schema->resultset('Track')->create({
+ trackid => 1, cd => 9999, position => 1, title => 'Track1'
+ });
+ } )
+ };
+
+ like $@, qr/violates foreign key constraint/i,
+ "Still expected exception on deferred failure at commit time";
+
+ } [], 'No warnings on deferred rollback';
}
done_testing;
END {
return unless $schema;
drop_test_schema($schema);
- eapk_drop_all( $schema)
+ eapk_drop_all($schema);
+ undef $schema;
};
position int,
title varchar(255),
last_updated_on date,
- last_updated_at date,
- small_dt date
+ last_updated_at date
)
EOS
for my $stat (
'DROP SCHEMA dbic_t_schema_5 CASCADE',
- 'DROP SEQUENCE public.artist_artistid_seq',
+ 'DROP SEQUENCE public.artist_artistid_seq CASCADE',
'DROP SCHEMA dbic_t_schema_4 CASCADE',
'DROP SCHEMA dbic_t_schema CASCADE',
- 'DROP SEQUENCE pkid1_seq',
- 'DROP SEQUENCE pkid2_seq',
- 'DROP SEQUENCE nonpkid_seq',
+ 'DROP SEQUENCE pkid1_seq CASCADE',
+ 'DROP SEQUENCE pkid2_seq CASCADE',
+ 'DROP SEQUENCE nonpkid_seq CASCADE',
'DROP SCHEMA dbic_t_schema_2 CASCADE',
'DROP SCHEMA dbic_t_schema_3 CASCADE',
) {