X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits%2FDBIx-Class.git;a=blobdiff_plain;f=t%2F73oracle.t;h=e7096ea4d4fd32c2099f0aacffa764f7a842f63c;hp=ca373cb51c6869c0a7ef7e2557cf9111e69a8614;hb=4c90556806f286093d0806e858abdba329e6dfd3;hpb=c7d50a7de2182fbfe933fa15b8015cb026f10c54 diff --git a/t/73oracle.t b/t/73oracle.t index ca373cb..e7096ea 100644 --- a/t/73oracle.t +++ b/t/73oracle.t @@ -1,3 +1,25 @@ +BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) } +use DBIx::Class::Optional::Dependencies -skip_all_without => 'test_rdbms_oracle'; + +use strict; +use warnings; + +use Test::Exception; +use Test::More; +use Try::Tiny; +use DBIx::Class::_Util 'set_subname'; + +use DBICTest; + +$ENV{NLS_SORT} = "BINARY"; +$ENV{NLS_COMP} = "BINARY"; +$ENV{NLS_LANG} = "AMERICAN"; + +my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_ORA_${_}" } qw/DSN USER PASS/}; + +# optional: +my ($dsn2, $user2, $pass2) = @ENV{map { "DBICTEST_ORA_EXTRAUSER_${_}" } qw/DSN USER PASS/}; + { package # hide from PAUSE DBICTest::Schema::ArtistFQN; @@ -23,38 +45,18 @@ data_type => 'integer', is_auto_increment => 1, }, + 'default_value_col' => { + data_type => 'varchar', + size => 100, + is_nullable => 0, + retrieve_on_insert => 1, + } ); __PACKAGE__->set_primary_key(qw/ artistid autoinc_col /); 1; } -use strict; -use warnings; - -use Test::Exception; -use Test::More; -use Sub::Name; - -use lib qw(t/lib); -use DBICTest; -use DBIC::SqlMakerTest; - -plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_rdbms_oracle') - unless DBIx::Class::Optional::Dependencies->req_ok_for ('test_rdbms_oracle'); - -$ENV{NLS_SORT} = "BINARY"; -$ENV{NLS_COMP} = "BINARY"; -$ENV{NLS_LANG} = "AMERICAN"; - -my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_ORA_${_}" } qw/DSN USER PASS/}; - -# optional: -my ($dsn2, $user2, $pass2) = @ENV{map { "DBICTEST_ORA_EXTRAUSER_${_}" } qw/DSN USER PASS/}; - -plan skip_all => 'Set $ENV{DBICTEST_ORA_DSN}, _USER and _PASS to run this test.' - unless ($dsn && $user && $pass); - DBICTest::Schema->load_classes('ArtistFQN'); # This is in Core now, but it's here just to test that it doesn't break @@ -64,67 +66,78 @@ DBICTest::Schema::CD->load_components('PK::Auto::Oracle'); DBICTest::Schema::Track->load_components('PK::Auto::Oracle'); +# check if we indeed do support stuff +my $v = do { + my $si = DBICTest::Schema->connect($dsn, $user, $pass)->storage->_server_info; + $si->{normalized_dbms_version} + or die "Unparseable Oracle server version: $si->{dbms_version}\n"; +}; + +my $test_server_supports_only_orajoins = $v < 9; + +# TODO find out which version supports the RETURNING syntax +# 8i (8.1) has it and earlier docs are a 404 on oracle.com +my $test_server_supports_insert_returning = $v >= 8.001; + +is ( + DBICTest::Schema->connect($dsn, $user, $pass)->storage->_use_insert_returning, + $test_server_supports_insert_returning, + 'insert returning capability guessed correctly' +); + +isa_ok (DBICTest::Schema->connect($dsn, $user, $pass)->storage->sql_maker, 'DBIx::Class::SQLMaker::Oracle'); + +# see if determining a driver with bad credentials throws propely +throws_ok { + DBICTest::Schema->connect($dsn, "BORKED BORKED USER $user", $pass)->storage->sql_maker; +} qr/DBI Connection failed/; + ########## -# recyclebin sometimes comes in the way -my $on_connect_sql = ["ALTER SESSION SET recyclebin = OFF"]; +# the recyclebin (new for 10g) sometimes comes in the way +my $on_connect_sql = $v >= 10 ? ["ALTER SESSION SET recyclebin = OFF"] : []; # iterate all tests on following options my @tryopt = ( { on_connect_do => $on_connect_sql }, - { quote_char => '"', on_connect_do => $on_connect_sql, }, + { quote_char => '"', on_connect_do => $on_connect_sql }, ); # keep a database handle open for cleanup my ($dbh, $dbh2); -# test insert returning - -# check if we indeed do support stuff -my $test_server_supports_insert_returning = do { - my $v = DBICTest::Schema->connect($dsn, $user, $pass) - ->storage - ->_get_dbh - ->get_info(18); - $v =~ /^(\d+)\.(\d+)/ - or die "Unparseable Oracle server version: $v\n"; - -# TODO find out which version supports the RETURNING syntax -# 8i has it and earlier docs are a 404 on oracle.com - ( $1 > 8 || ($1 == 8 && $2 >= 1) ) ? 1 : 0; -}; -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 - ? (1,0) - : (0) -) { - - no warnings qw/once/; - local *DBICTest::Schema::connection = subname 'DBICTest::Schema::connection' => sub { - my $s = shift->next::method (@_); - $s->storage->_use_insert_returning ($use_insert_returning); - $s; - }; - - for my $opt (@tryopt) { - # clean all cached sequences from previous run - for (map { values %{DBICTest::Schema->source($_)->columns_info} } (qw/Artist CD Track/) ) { - delete $_->{sequence}; - } +for my $use_insert_returning ($test_server_supports_insert_returning ? (1,0) : (0) ) { + for my $force_ora_joins ($test_server_supports_only_orajoins ? (0) : (0,1) ) { + + # doing it here instead of the actual class to keep the main thing under dfs + # and thus keep catching false positives (so far none, but one never knows) + mro::set_mro("DBICTest::Schema", "c3"); + + my $old_connection = DBICTest::Schema->can('connection'); + + no warnings qw/once redefine/; + local *DBICTest::Schema::connection = set_subname 'DBICTest::Schema::connection' => sub { + my $s = shift->$old_connection (@_); + $s->storage->_use_insert_returning ($use_insert_returning); + $s->storage->sql_maker_class('DBIx::Class::SQLMaker::OracleJoins') if $force_ora_joins; + $s; + }; + + for my $opt (@tryopt) { + # clean all cached sequences from previous run + for (map { values %{DBICTest::Schema->source($_)->columns_info} } (qw/Artist CD Track/) ) { + delete $_->{sequence}; + } - my $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opt); + my $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opt); - $dbh = $schema->storage->dbh; - my $q = $schema->storage->sql_maker->quote_char || ''; + $dbh = $schema->storage->dbh; + my $q = $schema->storage->sql_maker->quote_char || ''; - do_creates($dbh, $q); + do_creates($dbh, $q); - _run_tests($schema, $opt); + _run_tests($schema, $opt); + } } } @@ -172,6 +185,12 @@ sub _run_tests { is( $new->artistid, 3, "Oracle Auto-PK worked with fully-qualified tablename" ); is( $new->autoinc_col, 1000, "Oracle Auto-Inc overruled with fully-qualified tablename"); + + is( $new->default_value_col, 'default_value', $schema->storage->_use_insert_returning + ? 'Check retrieve_on_insert on default_value_col with INSERT ... RETURNING' + : 'Check retrieve_on_insert on default_value_col without INSERT ... RETURNING' + ); + SKIP: { skip 'not detecting sequences when using INSERT ... RETURNING', 1 if $schema->storage->_use_insert_returning; @@ -197,7 +216,6 @@ sub _run_tests { is( $it->next->name, "Artist 6", "iterator->next ok" ); is( $it->next, undef, "next past end of resultset ok" ); - # test identifiers over the 30 char limit lives_ok { my @results = $schema->resultset('CD')->search(undef, { @@ -220,11 +238,51 @@ sub _run_tests { is $query->first->cds_very_very_very_long_relationship_name->first->cdid, 1 } 'query with rel name over 30 chars survived and worked'; +# test rel names over the 30 char limit using group_by and join + { + my @group_cols = ( 'me.name' ); + my $query = $schema->resultset('Artist')->search({ + artistid => 1 + }, { + select => \@group_cols, + as => [map { /^\w+\.(\w+)$/ } @group_cols], + join => [qw( cds_very_very_very_long_relationship_name )], + group_by => \@group_cols, + }); + + lives_and { + my @got = $query->get_column('name')->all(); + is_deeply \@got, [$new_artist->name]; + } 'query with rel name over 30 chars worked on join, group_by for me col'; + + lives_and { + is $query->count(), 1 + } 'query with rel name over 30 chars worked on join, group_by, count for me col'; + } + { + my @group_cols = ( 'cds_very_very_very_long_relationship_name.title' ); + my $query = $schema->resultset('Artist')->search({ + artistid => 1 + }, { + select => \@group_cols, + as => [map { /^\w+\.(\w+)$/ } @group_cols], + join => [qw( cds_very_very_very_long_relationship_name )], + group_by => \@group_cols, + }); + + lives_and { + my @got = $query->get_column('title')->all(); + is_deeply \@got, [$new_cd->title]; + } 'query with rel name over 30 chars worked on join, group_by for long rel col'; + + lives_and { + is $query->count(), 1 + } 'query with rel name over 30 chars worked on join, group_by, count for long rel col'; + } + # rel name over 30 char limit with user condition # This requires walking the SQLA data structure. { - local $TODO = 'user condition on rel longer than 30 chars'; - $query = $schema->resultset('Artist')->search({ 'cds_very_very_very_long_relationship_name.title' => 'EP C' }, { @@ -298,7 +356,7 @@ sub _run_tests { } '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({ @@ -318,47 +376,92 @@ sub _run_tests { is($st->pkid1, 55, "Oracle Auto-PK without trigger: First primary key set manually"); -# test BLOBs - SKIP: { - TODO: { - my %binstr = ( 'small' => join('', map { chr($_) } ( 1 .. 127 )) ); - $binstr{'large'} = $binstr{'small'} x 1024; - - my $maxloblen = length $binstr{'large'}; - note "Localizing LongReadLen to $maxloblen to avoid truncation of test data"; - local $dbh->{'LongReadLen'} = $maxloblen; - - my $rs = $schema->resultset('BindType'); - my $id = 0; +# test populate (identity, success and error handling) + my $art_rs = $schema->resultset('Artist'); - if ($DBD::Oracle::VERSION eq '1.23') { - throws_ok { $rs->create({ id => 1, blob => $binstr{large} }) } - qr/broken/, - 'throws on blob insert with DBD::Oracle == 1.23'; - - skip 'buggy BLOB support in DBD::Oracle 1.23', 7; - } + my $seq_pos = $art_rs->get_column('artistid')->max; + ok($seq_pos, 'Starting with something in the artist table'); - # disable BLOB mega-output - my $orig_debug = $schema->storage->debug; - $schema->storage->debug (0); - local $TODO = 'Something is confusing column bindtype assignment when quotes are active' - if $q; + my $pop_rs = $schema->resultset('Artist')->search( + { name => { -like => 'pop_art_%' } }, + { order_by => 'artistid' } + ); - foreach my $type (qw( blob clob )) { - foreach my $size (qw( small large )) { - $id++; + $art_rs->delete; + lives_ok { + $pop_rs->populate([ + map { +{ name => "pop_art_$_" } } + (1,2,3) + ]); + + is_deeply ( + [ $pop_rs->get_column('artistid')->all ], + [ map { $seq_pos + $_ } (1,2,3) ], + 'Sequence works after empty-table insertion' + ); + } 'Populate without identity does not throw'; - lives_ok { $rs->create( { 'id' => $id, $type => $binstr{$size} } ) } - "inserted $size $type without dying"; - ok($rs->find($id)->$type eq $binstr{$size}, "verified inserted $size $type" ); - } - } + lives_ok { + $pop_rs->populate([ + map { +{ artistid => $_, name => "pop_art_$_" } } + (1,2,3) + ]); + + is_deeply ( + [ $pop_rs->get_column('artistid')->all ], + [ 1,2,3, map { $seq_pos + $_ } (1,2,3) ], + 'Explicit id population works' + ); + } 'Populate with identity does not throw'; - $schema->storage->debug ($orig_debug); - }} + throws_ok { + $pop_rs->populate([ + map { +{ artistid => $_, name => "pop_art_$_" } } + (200, 1, 300) + ]); + } qr/unique constraint.+populate slice.+name => "pop_art_1"/s, 'Partially failed populate throws'; + + is_deeply ( + [ $pop_rs->get_column('artistid')->all ], + [ 1,2,3, map { $seq_pos + $_ } (1,2,3) ], + 'Partially failed populate did not alter table contents' + ); +# test complex join (exercise orajoins) + lives_ok { is_deeply ( + $schema->resultset('CD')->search( + { 'artist.name' => 'pop_art_1', 'me.cdid' => { '!=', 999} }, + { join => 'artist', prefetch => 'tracks', rows => 4, order_by => 'tracks.trackid' } + )->all_hri, + [{ + artist => 1, + cdid => 1, + genreid => undef, + single_track => undef, + title => "EP C", + tracks => [ + { + cd => 1, + last_updated_at => undef, + last_updated_on => undef, + position => 1, + title => "Track1", + trackid => 1 + }, + { + cd => 1, + last_updated_at => undef, + last_updated_on => undef, + position => 1, + title => "Track2", + trackid => 2 + }, + ], + year => 2003 + }], + 'Correct set of data prefetched', + ) } 'complex prefetch ok'; # test sequence detection from a different schema SKIP: { @@ -371,11 +474,11 @@ sub _run_tests { skip 'not detecting cross-schema sequence name when using INSERT ... RETURNING', 1 if $schema->storage->_use_insert_returning; - # Oracle8i Reference Release 2 (8.1.6) + # Oracle8i Reference Release 2 (8.1.6) # http://download.oracle.com/docs/cd/A87860_01/doc/server.817/a76961/ch294.htm#993 # Oracle Database Reference 10g Release 2 (10.2) # http://download.oracle.com/docs/cd/B19306_01/server.102/b14237/statviews_2107.htm#sthref1297 - local $TODO = "On Oracle8i all_triggers view is empty, i don't yet know why..." + todo_skip "FIXME: On Oracle8i all_triggers view is empty, i don't yet know why...", 1 if $schema->storage->_server_info->{normalized_dbms_version} < 9; my $schema2 = $schema->connect($dsn2, $user2, $pass2, $opt); @@ -384,8 +487,9 @@ sub _run_tests { # create identically named tables/sequences in the other schema do_creates($dbh2, $q); - # grand select privileges to the 2nd user + # grant select privileges to the 2nd user $dbh->do("GRANT INSERT ON ${q}artist${q} TO " . uc $user2); + $dbh->do("GRANT SELECT ON ${q}artist${q} TO " . uc $user2); $dbh->do("GRANT SELECT ON ${q}artist_pk_seq${q} TO " . uc $user2); $dbh->do("GRANT SELECT ON ${q}artist_autoinc_seq${q} TO " . uc $user2); @@ -446,6 +550,26 @@ sub _run_tests { do_clean ($dbh2); }} +# test driver determination issues that led to the diagnosis/fix in 37b5ab51 +# observed side-effect when count-is-first on a fresh env-based connect + { + local $ENV{DBI_DSN}; + ($ENV{DBI_DSN}, my @user_pass_args) = @{ $schema->storage->connect_info }; + my $s2 = DBICTest::Schema->connect( undef, @user_pass_args ); + ok (! $s2->storage->connected, 'Not connected' ); + is (ref $s2->storage, 'DBIx::Class::Storage::DBI', 'Undetermined driver' ); + + ok ( + $s2->resultset('Artist')->search({ 'me.name' => { like => '%' } }, { prefetch => 'cds' })->count, + 'Some artist count' + ); + ok ( + scalar $s2->resultset('CD')->search({}, { join => 'tracks' } )->all, + 'Some cds returned' + ); + $s2->storage->disconnect; + } + do_clean ($dbh); } @@ -467,7 +591,7 @@ sub do_creates { # this one is always unquoted as per manually specified sequence => $dbh->do("CREATE SEQUENCE pkid2_seq START WITH 10 MAXVALUE 999999 MINVALUE 0"); - $dbh->do("CREATE TABLE ${q}artist${q} (${q}artistid${q} NUMBER(12), ${q}name${q} VARCHAR(255), ${q}autoinc_col${q} NUMBER(12), ${q}rank${q} NUMBER(38), ${q}charfield${q} VARCHAR2(10))"); + $dbh->do("CREATE TABLE ${q}artist${q} (${q}artistid${q} NUMBER(12), ${q}name${q} VARCHAR(255),${q}default_value_col${q} VARCHAR(255) DEFAULT 'default_value', ${q}autoinc_col${q} NUMBER(12), ${q}rank${q} NUMBER(38), ${q}charfield${q} VARCHAR2(10))"); $dbh->do("ALTER TABLE ${q}artist${q} ADD (CONSTRAINT ${q}artist_pk${q} PRIMARY KEY (${q}artistid${q}))"); $dbh->do("CREATE TABLE ${q}sequence_test${q} (${q}pkid1${q} NUMBER(12), ${q}pkid2${q} NUMBER(12), ${q}nonpkid${q} NUMBER(12), ${q}name${q} VARCHAR(255))"); @@ -480,8 +604,6 @@ sub do_creates { $dbh->do("CREATE TABLE ${q}track${q} (${q}trackid${q} NUMBER(12), ${q}cd${q} NUMBER(12) REFERENCES CD(${q}cdid${q}) DEFERRABLE, ${q}position${q} NUMBER(12), ${q}title${q} VARCHAR(255), ${q}last_updated_on${q} DATE, ${q}last_updated_at${q} DATE)"); $dbh->do("ALTER TABLE ${q}track${q} ADD (CONSTRAINT ${q}track_pk${q} PRIMARY KEY (${q}trackid${q}))"); - $dbh->do("CREATE TABLE ${q}bindtype_test${q} (${q}id${q} integer NOT NULL PRIMARY KEY, ${q}bytea${q} integer NULL, ${q}blob${q} blob NULL, ${q}clob${q} clob NULL)"); - $dbh->do(qq{ CREATE OR REPLACE TRIGGER ${q}artist_insert_trg_auto${q} BEFORE INSERT ON ${q}artist${q} @@ -576,6 +698,7 @@ END { next unless $_; local $SIG{__WARN__} = sub {}; do_clean($_); - $_->disconnect; } + undef $dbh; + undef $dbh2; }