From: Peter Rabbitson Date: Fri, 8 Jan 2010 18:19:41 +0000 (+0000) Subject: Merge 'trunk' into 'mssql_limit_regression' X-Git-Tag: v0.08116~61^2~8 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=25efdc9bcfd25ac45199b779498b37c02d6847a3;hp=d09c3ce73941decb9c4e7c544a81629eaa1a049f;p=dbsrgits%2FDBIx-Class.git Merge 'trunk' into 'mssql_limit_regression' r8076@Thesaurus (orig r8064): ribasushi | 2009-12-12 12:31:12 +0100 Even clearer unloaded FK exception r8078@Thesaurus (orig r8066): ribasushi | 2009-12-12 14:27:18 +0100 As clear as it gets r8141@Thesaurus (orig r8129): ovid | 2009-12-16 17:40:50 +0100 Have has_one/might_have warn if set on nullable columns. r8143@Thesaurus (orig r8131): caelum | 2009-12-17 13:30:10 +0100 somewhat better fix for ADO r8144@Thesaurus (orig r8132): caelum | 2009-12-17 13:34:20 +0100 minor changes r8146@Thesaurus (orig r8134): caelum | 2009-12-17 17:44:34 +0100 cleanup source_bind_attributes for ADO r8147@Thesaurus (orig r8135): caelum | 2009-12-17 18:09:55 +0100 more types for ADO fix, and documentation r8148@Thesaurus (orig r8136): abraxxa | 2009-12-17 19:54:55 +0100 Cookbook POD fix for add_drop_table instead of add_drop_tables r8158@Thesaurus (orig r8146): ribasushi | 2009-12-18 14:55:53 +0100 r8150@Thesaurus (orig r8138): abraxxa | 2009-12-17 23:22:07 +0100 Views without a view_definition won't be added to the SQL::Translator::Schema by the parser + tests r8151@Thesaurus (orig r8139): abraxxa | 2009-12-17 23:23:33 +0100 test cleanups r8153@Thesaurus (orig r8141): abraxxa | 2009-12-18 14:34:14 +0100 throw_exception if view_definition is missing instead of silent skipping + test changes r8154@Thesaurus (orig r8142): abraxxa | 2009-12-18 14:40:32 +0100 use Test::Exception r8155@Thesaurus (orig r8143): abraxxa | 2009-12-18 14:42:00 +0100 fixed Changes r8156@Thesaurus (orig r8144): abraxxa | 2009-12-18 14:44:52 +0100 test cleanups r8157@Thesaurus (orig r8145): ribasushi | 2009-12-18 14:46:26 +0100 Another bitr r8160@Thesaurus (orig r8148): ribasushi | 2009-12-18 15:04:34 +0100 Fix no_index entries r8162@Thesaurus (orig r8150): abraxxa | 2009-12-18 15:59:58 +0100 Schema POD inprovement for dclone r8163@Thesaurus (orig r8151): abraxxa | 2009-12-18 16:07:27 +0100 link to DBIx::Class::Row r8164@Thesaurus (orig r8152): abraxxa | 2009-12-18 16:08:56 +0100 fixed typo in Changes r8165@Thesaurus (orig r8153): abraxxa | 2009-12-18 16:14:47 +0100 dclone pod take #2 r8169@Thesaurus (orig r8157): ribasushi | 2009-12-19 18:47:42 +0100 detabify r8170@Thesaurus (orig r8158): ribasushi | 2009-12-19 19:41:42 +0100 Fix RT52812 r8171@Thesaurus (orig r8159): caelum | 2009-12-23 07:16:29 +0100 minor POD fixes r8175@Thesaurus (orig r8163): ribasushi | 2009-12-24 09:59:52 +0100 Fix deployment_statements context sensitivity regression r8176@Thesaurus (orig r8164): ribasushi | 2009-12-24 10:13:37 +0100 Don't call the PK setter if no PK r8204@Thesaurus (orig r8192): caelum | 2009-12-30 22:58:47 +0100 bump CAG dep r8231@Thesaurus (orig r8219): matthewt | 2010-01-02 01:41:12 +0100 fix typo in variable name r8238@Thesaurus (orig r8226): rafl | 2010-01-02 18:46:40 +0100 Merge branch 'native_traits' * native_traits: Port replicated storage from MXAH to native traits. Create branch native_traits r8244@Thesaurus (orig r8232): caelum | 2010-01-04 00:30:51 +0100 fix _rebless into sybase/mssql/nobindvars r8247@Thesaurus (orig r8235): caelum | 2010-01-05 13:54:56 +0100 r22328@hlagh (orig r8201): caelum | 2009-12-31 12:29:51 -0500 new branch to fix table aliases in queries over the 30char limit r22329@hlagh (orig r8202): caelum | 2009-12-31 12:55:50 -0500 failing test r22330@hlagh (orig r8203): caelum | 2009-12-31 13:00:35 -0500 switch oracle tests to done_testing() r22331@hlagh (orig r8204): caelum | 2009-12-31 15:02:50 -0500 got something working r22332@hlagh (orig r8205): caelum | 2009-12-31 15:08:30 -0500 POD touchups r22343@hlagh (orig r8216): caelum | 2010-01-01 07:42:03 -0500 fix uninitialized warning and a bug in ResultSet r22419@hlagh (orig r8234): caelum | 2010-01-05 07:53:18 -0500 append half of a base64 MD5 to shortened table aliases for Oracle r8249@Thesaurus (orig r8237): caelum | 2010-01-05 15:27:40 +0100 minor change: use more of the hash if possible for oracle table alias shortening r8251@Thesaurus (orig r8239): caelum | 2010-01-06 02:20:17 +0100 bump perl_version to 5.8.1 r8252@Thesaurus (orig r8240): caelum | 2010-01-06 02:21:41 +0100 remove alignment mark on base64 md5 r8260@Thesaurus (orig r8248): ribasushi | 2010-01-07 11:21:55 +0100 5.8.1 is minimum required perl r8261@Thesaurus (orig r8249): ribasushi | 2010-01-07 11:22:42 +0100 Minor optimization r8262@Thesaurus (orig r8250): ribasushi | 2010-01-07 11:23:35 +0100 Wrong title r8265@Thesaurus (orig r8253): ribasushi | 2010-01-08 17:48:50 +0100 Resolve problem reported by http://lists.scsys.co.uk/pipermail/dbix-class/2009-December/008699.html r8266@Thesaurus (orig r8254): ribasushi | 2010-01-08 17:52:01 +0100 Put utf8columns in line with the store_column fix r8267@Thesaurus (orig r8255): ribasushi | 2010-01-08 19:03:26 +0100 Tests while hunting for something else r8268@Thesaurus (orig r8256): ribasushi | 2010-01-08 19:14:42 +0100 Make test look even more like http://lists.scsys.co.uk/pipermail/dbix-class/2009-November/008599.html --- diff --git a/Changes b/Changes index bf9c1b0..3099a42 100644 --- a/Changes +++ b/Changes @@ -1,9 +1,17 @@ Revision history for DBIx::Class + - Perl 5.8.1 is now the minimum supported version + - might_have/has_one now warn if applied calling class's column + has is_nullable set to true. + - Fixed regression in deploy() with a {sources} table limit applied + (RT#52812) + - Cookbook POD fix for add_drop_table instead of add_drop_tables + - Views without a view_definition will throw an exception when + parsed by SQL::Translator::Parser::DBIx::Class + - Schema POD improvement for dclone + - Fix regression in context sensitiveness of deployment_statements - Fix regression resulting in overcomplicated query on search_related from prefetching resultsets - - Better isolation of RNO-limited queries from the rest - of a prefetching resultset 0.08115 2009-12-10 09:02:00 (CST) - Real limit/offset support for MSSQL server (via Row_Number) diff --git a/Makefile.PL b/Makefile.PL index 86497df..f96742b 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -3,12 +3,12 @@ use strict; use warnings; use POSIX (); -use 5.006001; # delete this line if you want to send patches for earlier. +use 5.008001; # ****** DO NOT ADD OPTIONAL DEPENDENCIES. EVER. --mst ****** name 'DBIx-Class'; -perl_version '5.006001'; +perl_version '5.008001'; all_from 'lib/DBIx/Class.pm'; @@ -26,17 +26,14 @@ requires 'List::Util' => '0'; requires 'Scalar::Util' => '0'; requires 'Storable' => '0'; -# Perl 5.8.0 doesn't have utf8::is_utf8() -requires 'Encode' => '0' if ($] <= 5.008000); - # Dependencies (keep in alphabetical order) requires 'Carp::Clan' => '6.0'; -requires 'Class::Accessor::Grouped' => '0.09000'; +requires 'Class::Accessor::Grouped' => '0.09002'; requires 'Class::C3::Componentised' => '1.0005'; requires 'Class::Inspector' => '1.24'; requires 'Data::Page' => '2.00'; requires 'DBD::SQLite' => '1.25'; -requires 'DBI' => '1.605'; +requires 'DBI' => '1.609'; requires 'JSON::Any' => '1.18'; requires 'MRO::Compat' => '0.09'; requires 'Module::Find' => '0.06'; @@ -48,8 +45,7 @@ requires 'Sub::Name' => '0.04'; requires 'Data::Dumper::Concise' => '1.000'; my %replication_requires = ( - 'Moose', => '0.87', - 'MooseX::AttributeHelpers' => '0.21', + 'Moose', => '0.90', 'MooseX::Types', => '0.16', 'namespace::clean' => '0.11', 'Hash::Merge', => '0.11', @@ -142,18 +138,15 @@ resources 'license' => 'http://dev.perl.org/licenses/'; resources 'repository' => 'http://dev.catalyst.perl.org/repos/bast/DBIx-Class/'; resources 'MailingList' => 'http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class'; -no_index 'DBIx::Class::SQLAHacks'; -no_index 'DBIx::Class::SQLAHacks::MySQL'; -no_index 'DBIx::Class::SQLAHacks::MSSQL'; -no_index 'DBIx::Class::SQLAHacks::OracleJoins'; -no_index 'DBIx::Class::Storage::DBI::AmbiguousGlob'; -no_index 'DBIx::Class::Storage::DBIHacks'; -no_index 'DBIx::Class::PK::Auto::DB2'; -no_index 'DBIx::Class::PK::Auto::MSSQL'; -no_index 'DBIx::Class::PK::Auto::MySQL'; -no_index 'DBIx::Class::PK::Auto::Oracle'; -no_index 'DBIx::Class::PK::Auto::Pg'; -no_index 'DBIx::Class::PK::Auto::SQLite'; +# Deprecated/internal modules need no exposure +no_index directory => $_ for (qw| + lib/DBIx/Class/SQLAHacks + lib/DBIx/Class/PK/Auto +|); +no_index package => $_ for (qw/ + DBIx::Class::Storage::DBI::AmbiguousGlob + DBIx::Class::SQLAHacks DBIx::Class::Storage::DBIHacks +/); # re-build README and require extra modules for testing if we're in a checkout diff --git a/lib/DBIx/Class.pm b/lib/DBIx/Class.pm index f6b0705..d5e742c 100644 --- a/lib/DBIx/Class.pm +++ b/lib/DBIx/Class.pm @@ -4,9 +4,10 @@ use strict; use warnings; use MRO::Compat; +use mro 'c3'; use vars qw($VERSION); -use base qw/Class::C3::Componentised Class::Accessor::Grouped/; +use base qw/DBIx::Class::Componentised Class::Accessor::Grouped/; use DBIx::Class::StartupCheck; sub mk_classdata { @@ -116,7 +117,7 @@ Then you can use these classes in your application's code: # Output all artists names # $artist here is a DBIx::Class::Row, which has accessors # for all its columns. Rows are also subclasses of your Result class. - foreach $artist (@artists) { + foreach $artist (@all_artists) { print $artist->name, "\n"; } @@ -294,6 +295,8 @@ norbi: Norbert Buchmuller Numa: Dan Sully +ovid: Curtis "Ovid" Poe + oyse: Øystein Torget paulm: Paul Makepeace diff --git a/lib/DBIx/Class/Componentised.pm b/lib/DBIx/Class/Componentised.pm index 7cb5d54..7b6813e 100644 --- a/lib/DBIx/Class/Componentised.pm +++ b/lib/DBIx/Class/Componentised.pm @@ -4,10 +4,34 @@ package # hide from PAUSE use strict; use warnings; -### -# Keep this class for backwards compatibility -### - use base 'Class::C3::Componentised'; +use Carp::Clan qw/^DBIx::Class|^Class::C3::Componentised/; +use mro 'c3'; + +# this warns of subtle bugs introduced by UTF8Columns hacky handling of store_column +sub inject_base { + my $class = shift; + my $target = shift; + + my @present_components = (@{mro::get_linear_isa ($target)||[]}); + + no strict 'refs'; + for my $comp (reverse @_) { + if ( + $comp->isa ('DBIx::Class::UTF8Columns') + and + my @broken = grep { $_ ne 'DBIx::Class::Row' and defined ${"${_}::"}{store_column} } (@present_components) + ) { + carp "Incorrect loading order of $comp by ${target} will affect other components overriding store_column (" + . join (', ', @broken) + .'). Refer to the documentation of DBIx::Class::UTF8Columns for more info'; + } + else { + unshift @present_components, $comp; + } + } + + $class->next::method($target, @_); +} 1; diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index ddac04a..7ae3630 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -390,8 +390,13 @@ and defining often used searches as methods: 1; -To use your resultset, first tell DBIx::Class to create an instance of it -for you, in your My::DBIC::Schema::CD class: +If you're using L, simply place the file +into the C directory next to your C directory, and it will +be automatically loaded. + +If however you are still using L, first tell +DBIx::Class to create an instance of the ResultSet class for you, in your +My::DBIC::Schema::CD class: # class definition as normal use base 'DBIx::Class::Core'; @@ -1381,7 +1386,7 @@ MySQL, SQLite and PostgreSQL, using the $VERSION from your Schema.pm. To create a new database using the schema: my $schema = My::Schema->connect($dsn); - $schema->deploy({ add_drop_tables => 1}); + $schema->deploy({ add_drop_table => 1}); To import created .sql files using the mysql client: diff --git a/lib/DBIx/Class/Relationship.pm b/lib/DBIx/Class/Relationship.pm index 3351391..e1bed80 100644 --- a/lib/DBIx/Class/Relationship.pm +++ b/lib/DBIx/Class/Relationship.pm @@ -441,6 +441,17 @@ methods and valid relationship attributes. Also see L for a L which can be assigned to relationships as well. +Note that if you supply a condition on which to join, if the column in the +current table allows nulls (i.e., has the C attribute set to a +true value), than C will warn about this because it's naughty and +you shouldn't do that. + + "might_have/has_one" must not be on columns with is_nullable set to true (MySchema::SomeClass/key) + +If you must be naughty, you can suppress the warning by setting +C environment variable to a true value. Otherwise, +you probably just want to use C. + =head2 has_one =over 4 @@ -528,6 +539,11 @@ methods and valid relationship attributes. Also see L for a L which can be assigned to relationships as well. +Note that if you supply a condition on which to join, if the column in the +current table allows nulls (i.e., has the C attribute set to a +true value), than warnings might apply just as with +L. + =head2 many_to_many =over 4 diff --git a/lib/DBIx/Class/Relationship/HasOne.pm b/lib/DBIx/Class/Relationship/HasOne.pm index 4c910b8..73cb929 100644 --- a/lib/DBIx/Class/Relationship/HasOne.pm +++ b/lib/DBIx/Class/Relationship/HasOne.pm @@ -3,6 +3,7 @@ package # hide from PAUSE use strict; use warnings; +use Carp::Clan qw/^DBIx::Class/; our %_pod_inherit_config = ( @@ -21,20 +22,16 @@ sub _has_one { my ($class, $join_type, $rel, $f_class, $cond, $attrs) = @_; unless (ref $cond) { $class->ensure_class_loaded($f_class); - my ($pri, $too_many) = $class->primary_columns; - - $class->throw_exception( - "might_have/has_one can only infer join for a single primary key; ". - "${class} has more" - ) if $too_many; + my $pri = $class->_get_primary_key; + $class->throw_exception( "might_have/has_one needs a primary key to infer a join; ". "${class} has none" ) if !defined $pri && (!defined $cond || !length $cond); my $f_class_loaded = eval { $f_class->columns }; - my ($f_key,$guess); + my ($f_key,$too_many,$guess); if (defined $cond && length $cond) { $f_key = $cond; $guess = "caller specified foreign key '$f_key'"; @@ -42,11 +39,7 @@ sub _has_one { $f_key = $rel; $guess = "using given relationship '$rel' for foreign key"; } else { - ($f_key, $too_many) = $f_class->primary_columns; - $class->throw_exception( - "might_have/has_one can only infer join for a single primary key; ". - "${f_class} has more" - ) if $too_many; + $f_key = $class->_get_primary_key($f_class); $guess = "using primary key of foreign class for foreign key"; } $class->throw_exception( @@ -54,6 +47,7 @@ sub _has_one { ) if $f_class_loaded && !$f_class->has_column($f_key); $cond = { "foreign.${f_key}" => "self.${pri}" }; } + $class->_validate_cond($cond); $class->add_relationship($rel, $f_class, $cond, { accessor => 'single', @@ -63,4 +57,34 @@ sub _has_one { 1; } +sub _get_primary_key { + my ( $class, $target_class ) = @_; + $target_class ||= $class; + my ($pri, $too_many) = $target_class->primary_columns; + $class->throw_exception( + "might_have/has_one can only infer join for a single primary key; ". + "${class} has more" + ) if $too_many; + return $pri; +} + +sub _validate_cond { + my ($class, $cond ) = @_; + + return if $ENV{DBIC_DONT_VALIDATE_RELS}; + return unless 'HASH' eq ref $cond; + foreach my $foreign_id ( keys %$cond ) { + my $self_id = $cond->{$foreign_id}; + + # we can ignore a bad $self_id because add_relationship handles this + # warning + return unless $self_id =~ /^self\.(.*)$/; + my $key = $1; + my $column_info = $class->column_info($key); + if ( $column_info->{is_nullable} ) { + carp(qq'"might_have/has_one" must not be on columns with is_nullable set to true ($class/$key) '); + } + } +} + 1; diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 6d0486b..ae55dfb 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -1431,7 +1431,7 @@ sub _rs_update_delete { my $cond = $rsrc->schema->storage->_strip_cond_qualifiers ($self->{cond}); my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/); - my $needs_subq = (not defined $cond) || $self->_has_resolved_attr(qw/row offset/); + my $needs_subq = $needs_group_by_subq || (not defined $cond) || $self->_has_resolved_attr(qw/row offset/); if ($needs_group_by_subq or $needs_subq) { @@ -2522,7 +2522,9 @@ sub related_resultset { my $attrs = $self->_chain_relationship($rel); my $join_count = $attrs->{seen_join}{$rel}; - my $alias = ($join_count > 1 ? join('_', $rel, $join_count) : $rel); + + my $alias = $self->result_source->storage + ->relname_to_table_alias($rel, $join_count); #XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi delete @{$attrs}{qw(result_class alias)}; diff --git a/lib/DBIx/Class/ResultSource.pm b/lib/DBIx/Class/ResultSource.pm index 6945f48..541aa12 100644 --- a/lib/DBIx/Class/ResultSource.pm +++ b/lib/DBIx/Class/ResultSource.pm @@ -1228,7 +1228,9 @@ sub _resolve_join { $force_left ||= lc($rel_info->{attrs}{join_type}||'') eq 'left'; # the actual seen value will be incremented by the recursion - my $as = ($seen->{$rel} ? join ('_', $rel, $seen->{$rel} + 1) : $rel); + my $as = $self->storage->relname_to_table_alias( + $rel, ($seen->{$rel} && $seen->{$rel} + 1) + ); push @ret, ( $self->_resolve_join($rel, $alias, $seen, [@$jpath], $force_left), @@ -1245,7 +1247,9 @@ sub _resolve_join { } else { my $count = ++$seen->{$join}; - my $as = ($count > 1 ? "${join}_${count}" : $join); + my $as = $self->storage->relname_to_table_alias( + $join, ($count > 1 && $count) + ); my $rel_info = $self->relationship_info($join) or $self->throw_exception("No such relationship ${join}"); @@ -1334,13 +1338,13 @@ sub _resolve_condition { unless ($for->has_column_loaded($v)) { if ($for->in_storage) { $self->throw_exception(sprintf - 'Unable to resolve relationship from %s to %s: column %s.%s not ' - . 'loaded from storage (or not passed to new() prior to insert()). ' - . 'Maybe you forgot to call ->discard_changes to get defaults from the db.', - - $for->result_source->source_name, + "Unable to resolve relationship '%s' from object %s: column '%s' not " + . 'loaded from storage (or not passed to new() prior to insert()). You ' + . 'probably need to call ->discard_changes to get the server-side defaults ' + . 'from the database.', $as, - $as, $v, + $for, + $v, ); } return $UNRESOLVABLE_CONDITION; diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index a6dcbeb..ac496f8 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -802,7 +802,7 @@ sub set_column { $self->{_orig_ident} ||= $self->ident_condition; my $old_value = $self->get_column($column); - $self->store_column($column, $new_value); + $new_value = $self->store_column($column, $new_value); my $dirty; if (!$self->in_storage) { # no point tracking dirtyness on uninserted data diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 4c3a0ad..3aa429a 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -1178,8 +1178,17 @@ sub freeze { =head2 dclone -Recommeneded way of dcloning objects. This is needed to properly maintain -references to the schema object (which itself is B cloned.) +=over 4 + +=item Arguments: $object + +=item Return Value: dcloned $object + +=back + +Recommended way of dcloning L and L +objects so their references to the schema object +(which itself is B cloned) are properly maintained. =cut diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index dd8616d..f621aad 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -1290,12 +1290,6 @@ sub _query_end { } } -sub _sth_bind_param { - my ($self, $sth, $placeholder_index, $data, $attributes) = @_; - - $sth->bind_param($placeholder_index, $data, $attributes); -} - sub _dbh_execute { my ($self, $dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_; @@ -1320,7 +1314,7 @@ sub _dbh_execute { my $ref = ref $data; $data = $ref && $ref ne 'ARRAY' ? ''.$data : $data; # stringify args (except arrayrefs) - $self->_sth_bind_param($sth, $placeholder_index, $data, $attributes); + $sth->bind_param($placeholder_index, $data, $attributes); $placeholder_index++; } } @@ -2391,10 +2385,19 @@ sub deployment_statements { data => $schema, ); - my $ret = $tr->translate - or $self->throw_exception( 'Unable to produce deployment statements: ' . $tr->error); + my @ret; + my $wa = wantarray; + if ($wa) { + @ret = $tr->translate; + } + else { + $ret[0] = $tr->translate; + } - return $ret; + $self->throw_exception( 'Unable to produce deployment statements: ' . $tr->error) + unless (@ret && defined $ret[0]); + + return $wa ? @ret : $ret[0]; } sub deploy { @@ -2519,6 +2522,34 @@ sub lag_behind_master { sub _sqlt_minimum_version { $minimum_sqlt_version }; } +=head2 relname_to_table_alias + +=over 4 + +=item Arguments: $relname, $join_count + +=back + +L uses L names as table aliases in +queries. + +This hook is to allow specific L drivers to change the +way these aliases are named. + +The default behavior is C<"$relname_$join_count" if $join_count > 1>, otherwise +C<"$relname">. + +=cut + +sub relname_to_table_alias { + my ($self, $relname, $join_count) = @_; + + my $alias = ($join_count && $join_count > 1 ? + join('_', $relname, $join_count) : $relname); + + return $alias; +} + sub DESTROY { my $self = shift; diff --git a/lib/DBIx/Class/Storage/DBI/ADO.pm b/lib/DBIx/Class/Storage/DBI/ADO.pm index 8a0fa68..e457b96 100644 --- a/lib/DBIx/Class/Storage/DBI/ADO.pm +++ b/lib/DBIx/Class/Storage/DBI/ADO.pm @@ -26,7 +26,8 @@ sub _rebless { } } -# set cursor type here, if necessary +# Here I was just experimenting with ADO cursor types, left in as a comment in +# case you want to as well. See the DBD::ADO docs. #sub _dbh_sth { # my ($self, $dbh, $sql) = @_; # diff --git a/lib/DBIx/Class/Storage/DBI/ADO/Microsoft_SQL_Server.pm b/lib/DBIx/Class/Storage/DBI/ADO/Microsoft_SQL_Server.pm index b4bb2e9..90d7639 100644 --- a/lib/DBIx/Class/Storage/DBI/ADO/Microsoft_SQL_Server.pm +++ b/lib/DBIx/Class/Storage/DBI/ADO/Microsoft_SQL_Server.pm @@ -14,12 +14,84 @@ sub _rebless { $self->_identity_method('@@identity'); } -sub _sth_bind_param { - my ($self, $sth, $placeholder_index, $data, $attributes, @extra) = @_; +sub source_bind_attributes { + my $self = shift; + my ($source) = @_; + + my $bind_attributes = $self->next::method(@_); + + foreach my $column ($source->columns) { + $bind_attributes->{$column}{ado_size} ||= 8000; # max VARCHAR + } + + return $bind_attributes; +} - $attributes->{ado_size} = 8000; # max VARCHAR on MSSQL +sub bind_attribute_by_data_type { + my ($self, $data_type) = @_; - $self->next::method($sth, $placeholder_index, $data, $attributes, @extra); + ($data_type = lc($data_type)) =~ s/\s+.*//; + + my $max_size = + $self->_mssql_max_data_type_representation_size_in_bytes->{$data_type}; + + my $res = {}; + $res->{ado_size} = $max_size if $max_size; + + return $res; +} + +# approximate +# XXX needs to support varchar(max) and varbinary(max) +sub _mssql_max_data_type_representation_size_in_bytes { + my $self = shift; + + my $blob_max = $self->_get_dbh->{LongReadLen} || 32768; + + return +{ +# MSSQL types + char => 8000, + varchar => 8000, + binary => 8000, + varbinary => 8000, + nchar => 8000, + nvarchar => 8000, + numeric => 100, + smallint => 100, + tinyint => 100, + smallmoney => 100, + bigint => 100, + bit => 100, + decimal => 100, + integer => 100, + int => 100, + money => 100, + float => 100, + real => 100, + uniqueidentifier => 100, + ntext => $blob_max, + text => $blob_max, + image => $blob_max, + date => 100, + datetime => 100, + datetime2 => 100, + datetimeoffset => 100, + smalldatetime => 100, + time => 100, + timestamp => 100, + cursor => 100, + hierarchyid => 100, + sql_variant => 100, + table => 100, + xml => $blob_max, # ??? + +# some non-MSSQL types + serial => 100, + bigserial => 100, + varchar2 => 8000, + blob => $blob_max, + clob => $blob_max, + } } 1; @@ -38,10 +110,29 @@ This subclass supports MSSQL server connections via L. The MSSQL specific functionality is provided by L. +=head2 CAVEATS + +=head3 identities + C<_identity_method> is set to C<@@identity>, as C doesn't work with L. See L for caveats regarding this. +=head3 truncation bug + +There is a bug with MSSQL ADO providers where data gets truncated based on the +size of the bind sizes in the first prepare call: + +L + +The C workaround is used (see L) with the +approximate maximum size of the data_type of the bound column, or 8000 (maximum +VARCHAR size) if the data_type is not available. + +This code is incomplete and may be buggy. Particularly, C is not +supported yet. The data_type list for other DBs is also incomplete. Please +report problems (and send patches.) + =head1 AUTHOR See L. diff --git a/lib/DBIx/Class/Storage/DBI/AmbiguousGlob.pm b/lib/DBIx/Class/Storage/DBI/AmbiguousGlob.pm index 37d1bd6..d883d0b 100644 --- a/lib/DBIx/Class/Storage/DBI/AmbiguousGlob.pm +++ b/lib/DBIx/Class/Storage/DBI/AmbiguousGlob.pm @@ -8,7 +8,7 @@ use mro 'c3'; =head1 NAME -DBIx::Class::Storage::DBI::AmbiguousGlob - Storage component for RDBMS supporting multicolumn in clauses +DBIx::Class::Storage::DBI::AmbiguousGlob - Storage component for RDBMS choking on count(*) =head1 DESCRIPTION diff --git a/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm b/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm index 2b62826..fe81851 100644 --- a/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm +++ b/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm @@ -17,7 +17,9 @@ DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class =head1 DESCRIPTION -This class implements autoincrements for Oracle. +This class implements base Oracle support. The subclass +L is for C<(+)> joins in Oracle +versions before 9. =head1 METHODS @@ -274,6 +276,46 @@ sub _svp_rollback { $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name") } +=head2 relname_to_table_alias + +L uses L names as table aliases in +queries. + +Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so +the L name is shortened and appended with half of an +MD5 hash. + +See L. + +=cut + +sub relname_to_table_alias { + my $self = shift; + my ($relname, $join_count) = @_; + + my $alias = $self->next::method(@_); + + return $alias if length($alias) <= 30; + + # get a base64 md5 of the alias with join_count + require Digest::MD5; + my $ctx = Digest::MD5->new; + $ctx->add($alias); + my $md5 = $ctx->b64digest; + + # remove alignment mark just in case + $md5 =~ s/=*\z//; + + # truncate and prepend to truncated relname without vowels + (my $devoweled = $relname) =~ s/[aeiou]//g; + my $shortened = substr($devoweled, 0, 18); + + my $new_alias = + $shortened . '_' . substr($md5, 0, 30 - length($shortened) - 1); + + return $new_alias; +} + =head1 AUTHOR See L. diff --git a/lib/DBIx/Class/Storage/DBI/Replicated.pm b/lib/DBIx/Class/Storage/DBI/Replicated.pm index 1589b5f..d8a5f6d 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated.pm @@ -7,8 +7,7 @@ BEGIN { ## use, so we explicitly test for these. my %replication_required = ( - 'Moose' => '0.87', - 'MooseX::AttributeHelpers' => '0.21', + 'Moose' => '0.90', 'MooseX::Types' => '0.16', 'namespace::clean' => '0.11', 'Hash::Merge' => '0.11' @@ -119,8 +118,7 @@ to force a query to run against Master when needed. Replicated Storage has additional requirements not currently part of L - Moose => '0.87', - MooseX::AttributeHelpers => '0.20', + Moose => '0.90', MooseX::Types => '0.16', namespace::clean => '0.11', Hash::Merge => '0.11' diff --git a/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm b/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm index e5fa1a1..a7a1dfa 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm @@ -1,7 +1,6 @@ package DBIx::Class::Storage::DBI::Replicated::Pool; use Moose; -use MooseX::AttributeHelpers; use DBIx::Class::Storage::DBI::Replicated::Replicant; use List::Util 'sum'; use Scalar::Util 'reftype'; @@ -125,26 +124,31 @@ removes the replicant under $key from the pool has 'replicants' => ( is=>'rw', - metaclass => 'Collection::Hash', + traits => ['Hash'], isa=>HashRef['Object'], default=>sub {{}}, - provides => { - 'set' => 'set_replicant', - 'get' => 'get_replicant', - 'empty' => 'has_replicants', - 'count' => 'num_replicants', - 'delete' => 'delete_replicant', - 'values' => 'all_replicant_storages', + handles => { + 'set_replicant' => 'set', + 'get_replicant' => 'get', + 'has_replicants' => 'is_empty', + 'num_replicants' => 'count', + 'delete_replicant' => 'delete', + 'all_replicant_storages' => 'values', }, ); +around has_replicants => sub { + my ($orig, $self) = @_; + return !$self->$orig; +}; + has next_unknown_replicant_id => ( is => 'rw', - metaclass => 'Counter', + traits => ['Counter'], isa => Int, default => 1, - provides => { - inc => 'inc_unknown_replicant_id' + handles => { + 'inc_unknown_replicant_id' => 'inc', }, ); diff --git a/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm index 9666a00..0173fac 100644 --- a/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm +++ b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm @@ -13,7 +13,11 @@ sub _rebless { my $self = shift; my $dbh = $self->_get_dbh; + return if ref $self ne __PACKAGE__; + if (not $self->_typeless_placeholders_supported) { + require + DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars; bless $self, 'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars'; $self->_rebless; diff --git a/lib/DBIx/Class/UTF8Columns.pm b/lib/DBIx/Class/UTF8Columns.pm index b40f676..7e21502 100644 --- a/lib/DBIx/Class/UTF8Columns.pm +++ b/lib/DBIx/Class/UTF8Columns.pm @@ -2,17 +2,7 @@ package DBIx::Class::UTF8Columns; use strict; use warnings; use base qw/DBIx::Class/; - -BEGIN { - - # Perl 5.8.0 doesn't have utf8::is_utf8() - # Yes, 5.8.0 support for Unicode is suboptimal, but things like RHEL3 ship with it. - if ($] <= 5.008000) { - require Encode; - } else { - require utf8; - } -} +use utf8; __PACKAGE__->mk_classdata( '_utf8_columns' ); @@ -36,6 +26,15 @@ DBIx::Class::UTF8Columns - Force UTF8 (Unicode) flag on columns This module allows you to get columns data that have utf8 (Unicode) flag. +=head2 Warning + +Note that this module overloads L in a way +that may prevent other components overloading the same method from working +correctly. This component must be the last one before L +(which is provided by L). DBIx::Class will detect such +incorrect component order and issue an appropriate warning, advising which +components need to be loaded differently. + =head1 SEE ALSO L, L. @@ -52,7 +51,7 @@ sub utf8_columns { foreach my $col (@_) { $self->throw_exception("column $col doesn't exist") unless $self->has_column($col); - } + } return $self->_utf8_columns({ map { $_ => 1 } @_ }); } else { return $self->_utf8_columns; @@ -69,17 +68,11 @@ sub get_column { my ( $self, $column ) = @_; my $value = $self->next::method($column); - my $cols = $self->_utf8_columns; - if ( $cols and defined $value and $cols->{$column} ) { + utf8::decode($value) if ( + defined $value and $self->_is_utf8_column($column) and ! utf8::is_utf8($value) + ); - if ($] <= 5.008000) { - Encode::_utf8_on($value) unless Encode::is_utf8($value); - } else { - utf8::decode($value) unless utf8::is_utf8($value); - } - } - - $value; + return $value; } =head2 get_columns @@ -90,16 +83,13 @@ sub get_columns { my $self = shift; my %data = $self->next::method(@_); - foreach my $col (grep { defined $data{$_} } keys %{ $self->_utf8_columns || {} }) { - - if ($] <= 5.008000) { - Encode::_utf8_on($data{$col}) unless Encode::is_utf8($data{$col}); - } else { - utf8::decode($data{$col}) unless utf8::is_utf8($data{$col}); - } + foreach my $col (keys %data) { + utf8::decode($data{$col}) if ( + exists $data{$col} and defined $data{$col} and $self->_is_utf8_column($col) and ! utf8::is_utf8($data{$col}) + ); } - %data; + return %data; } =head2 store_column @@ -109,32 +99,32 @@ sub get_columns { sub store_column { my ( $self, $column, $value ) = @_; - my $cols = $self->_utf8_columns; - if ( $cols and defined $value and $cols->{$column} ) { + # the dirtyness comparison must happen on the non-encoded value + my $copy; - if ($] <= 5.008000) { - Encode::_utf8_off($value) if Encode::is_utf8($value); - } else { - utf8::encode($value) if utf8::is_utf8($value); - } + if ( defined $value and $self->_is_utf8_column($column) and utf8::is_utf8($value) ) { + $copy = $value; + utf8::encode($value); } $self->next::method( $column, $value ); + + return $copy || $value; } -=head1 AUTHOR +# override this if you want to force everything to be encoded/decoded +sub _is_utf8_column { + return (shift->utf8_columns || {})->{shift}; +} -Daisuke Murase +=head1 AUTHORS -=head1 COPYRIGHT +See L. -This program is free software; you can redistribute -it and/or modify it under the same terms as Perl itself. +=head1 LICENSE -The full text of the license can be found in the -LICENSE file included with this module. +You may distribute this code under the same terms as Perl itself. =cut 1; - diff --git a/lib/SQL/Translator/Parser/DBIx/Class.pm b/lib/SQL/Translator/Parser/DBIx/Class.pm index bb40c91..705d380 100644 --- a/lib/SQL/Translator/Parser/DBIx/Class.pm +++ b/lib/SQL/Translator/Parser/DBIx/Class.pm @@ -65,19 +65,19 @@ sub parse { } - my(@table_monikers, @view_monikers); + my(%table_monikers, %view_monikers); for my $moniker (@monikers){ my $source = $dbicschema->source($moniker); if ( $source->isa('DBIx::Class::ResultSource::Table') ) { - push(@table_monikers, $moniker); + $table_monikers{$moniker}++; } elsif( $source->isa('DBIx::Class::ResultSource::View') ){ next if $source->is_virtual; - push(@view_monikers, $moniker); + $view_monikers{$moniker}++; } } my %tables; - foreach my $moniker (sort @table_monikers) + foreach my $moniker (sort keys %table_monikers) { my $source = $dbicschema->source($moniker); my $table_name = $source->name; @@ -112,9 +112,11 @@ sub parse { my $f = $table->add_field(%colinfo) || $dbicschema->throw_exception ($table->error); } - $table->primary_key($source->primary_columns); my @primary = $source->primary_columns; + + $table->primary_key(@primary) if @primary; + my %unique_constraints = $source->unique_constraints; foreach my $uniq (sort keys %unique_constraints) { if (!$source->_compare_relationship_keys($unique_constraints{$uniq}, \@primary)) { @@ -131,18 +133,22 @@ sub parse { my %created_FK_rels; # global add_fk_index set in parser_args - my $add_fk_index = (exists $args->{add_fk_index} && ($args->{add_fk_index} == 0)) ? 0 : 1; + my $add_fk_index = (exists $args->{add_fk_index} && ! $args->{add_fk_index}) ? 0 : 1; foreach my $rel (sort @rels) { + my $rel_info = $source->relationship_info($rel); # Ignore any rel cond that isn't a straight hash next unless ref $rel_info->{cond} eq 'HASH'; - my $othertable = $source->related_source($rel); - next if $othertable->isa('DBIx::Class::ResultSource::View'); # can't define constraints referencing a view - my $rel_table = $othertable->name; + my $relsource = $source->related_source($rel); + + # related sources might be excluded via a {sources} filter or might be views + next unless exists $table_monikers{$relsource->source_name}; + + my $rel_table = $relsource->name; # FIXME - this isn't the right way to do it, but sqlt does not # support quoting properly to be signaled about this @@ -153,7 +159,7 @@ sub parse { # Force the order of @cond to match the order of ->add_columns my $idx; - my %other_columns_idx = map {'foreign.'.$_ => ++$idx } $othertable->columns; + my %other_columns_idx = map {'foreign.'.$_ => ++$idx } $relsource->columns; my @cond = sort { $other_columns_idx{$a} cmp $other_columns_idx{$b} } keys(%{$rel_info->{cond}}); # Get the key information, mapping off the foreign/self markers @@ -210,11 +216,12 @@ sub parse { my $is_deferrable = $rel_info->{attrs}{is_deferrable}; - # do not consider deferrable constraints and self-references - # for dependency calculations + # calculate dependencies: do not consider deferrable constraints and + # self-references for dependency calculations if (! $is_deferrable and $rel_table ne $table_name) { $tables{$table_name}{foreign_table_deps}{$rel_table}++; } + $table->add_constraint( type => 'foreign_key', name => join('_', $table_name, 'fk', @keys), @@ -274,7 +281,7 @@ EOW } my %views; - foreach my $moniker (sort @view_monikers) + foreach my $moniker (sort keys %view_monikers) { my $source = $dbicschema->source($moniker); my $view_name = $source->name; @@ -289,6 +296,9 @@ EOW # Its possible to have multiple DBIC source using same table next if $views{$view_name}++; + $dbicschema->throw_exception ("view $view_name is missing a view_definition") + unless $source->view_definition; + my $view = $schema->add_view ( name => $view_name, fields => [ $source->columns ], diff --git a/t/51threads.t b/t/51threads.t index a7a3a78..4cb7bec 100644 --- a/t/51threads.t +++ b/t/51threads.t @@ -8,7 +8,7 @@ use Config; BEGIN { plan skip_all => 'Your perl does not support ithreads' - if !$Config{useithreads} || $] < 5.008; + if !$Config{useithreads}; } use threads; diff --git a/t/51threadtxn.t b/t/51threadtxn.t index 3cc6779..eb3ee6a 100644 --- a/t/51threadtxn.t +++ b/t/51threadtxn.t @@ -8,7 +8,7 @@ use Config; BEGIN { plan skip_all => 'Your perl does not support ithreads' - if !$Config{useithreads} || $] < 5.008; + if !$Config{useithreads}; } use threads; diff --git a/t/60core.t b/t/60core.t index be22fa7..b1503ca 100644 --- a/t/60core.t +++ b/t/60core.t @@ -109,10 +109,12 @@ is($new_again->ID, 'DBICTest::Artist|artist|artistid=4', 'unique object id gener { ok(my $artist = $schema->resultset('Artist')->create({name => 'store_column test'})); is($artist->name, 'X store_column test'); # used to be 'X X store...' - + # call store_column even though the column doesn't seem to be dirty - ok($artist->update({name => 'X store_column test'})); + $artist->name($artist->name); is($artist->name, 'X X store_column test'); + ok($artist->is_column_changed('name'), 'changed column marked as dirty'); + $artist->delete; } diff --git a/t/71mysql.t b/t/71mysql.t index 5c5d53c..aa2db86 100644 --- a/t/71mysql.t +++ b/t/71mysql.t @@ -243,11 +243,11 @@ ZEROINSEARCH: { is ($rs->count, 6, 'CDs created successfully'); $rs = $rs->search ({}, { - select => [ {year => 'year'} ], as => ['y'], distinct => 1, order_by => 'year', + select => [ \ 'YEAR(year)' ], as => ['y'], distinct => 1, }); is_deeply ( - [ $rs->get_column ('y')->all ], + [ sort ($rs->get_column ('y')->all) ], [ sort keys %$cds_per_year ], 'Years group successfully', ); @@ -255,7 +255,7 @@ ZEROINSEARCH: { $rs->create ({ artist => 1, year => '0-1-1', title => 'Jesus Rap' }); is_deeply ( - [ $rs->get_column ('y')->all ], + [ sort $rs->get_column ('y')->all ], [ 0, sort keys %$cds_per_year ], 'Zero-year groups successfully', ); diff --git a/t/73oracle.t b/t/73oracle.t index bb5a86e..04f1641 100644 --- a/t/73oracle.t +++ b/t/73oracle.t @@ -40,8 +40,6 @@ plan skip_all => 'Set $ENV{DBICTEST_ORA_DSN}, _USER and _PASS to run this test. ' as well as following sequences: \'pkid1_seq\', \'pkid2_seq\' and \'nonpkid_seq\'' unless ($dsn && $user && $pass); -plan tests => 36; - DBICTest::Schema->load_classes('ArtistFQN'); my $schema = DBICTest::Schema->connect($dsn, $user, $pass); @@ -65,7 +63,7 @@ $dbh->do("CREATE SEQUENCE pkid2_seq START WITH 10 MAXVALUE 999999 MINVALUE 0"); $dbh->do("CREATE SEQUENCE nonpkid_seq START WITH 20 MAXVALUE 999999 MINVALUE 0"); $dbh->do("CREATE TABLE artist (artistid NUMBER(12), name VARCHAR(255), rank NUMBER(38), charfield VARCHAR2(10))"); $dbh->do("CREATE TABLE sequence_test (pkid1 NUMBER(12), pkid2 NUMBER(12), nonpkid NUMBER(12), name VARCHAR(255))"); -$dbh->do("CREATE TABLE cd (cdid NUMBER(12), artist NUMBER(12), title VARCHAR(255), year VARCHAR(4))"); +$dbh->do("CREATE TABLE cd (cdid NUMBER(12), artist NUMBER(12), title VARCHAR(255), year VARCHAR(4), genreid NUMBER(12), single_track NUMBER(12))"); $dbh->do("CREATE TABLE track (trackid NUMBER(12), cd NUMBER(12), position NUMBER(12), title VARCHAR(255), last_updated_on DATE, last_updated_at DATE, small_dt DATE)"); $dbh->do("ALTER TABLE artist ADD (CONSTRAINT artist_pk PRIMARY KEY (artistid))"); @@ -124,12 +122,39 @@ my $new = $schema->resultset('Artist')->create({ name => 'foo' }); is($new->artistid, 1, "Oracle Auto-PK worked"); my $cd = $schema->resultset('CD')->create({ artist => 1, title => 'EP C', year => '2003' }); -is($new->artistid, 1, "Oracle Auto-PK worked - using scalar ref as table name"); +is($cd->cdid, 1, "Oracle Auto-PK worked - using scalar ref as table name"); # test again with fully-qualified table name $new = $schema->resultset('ArtistFQN')->create( { name => 'bar' } ); is( $new->artistid, 2, "Oracle Auto-PK worked with fully-qualified tablename" ); +# test rel names over the 30 char limit +my $query = $schema->resultset('Artist')->search({ + artistid => 1 +}, { + prefetch => 'cds_very_very_very_long_relationship_name' +}); + +lives_and { + is $query->first->cds_very_very_very_long_relationship_name->first->cdid, 1 +} 'query with rel name over 30 chars survived and worked'; + +# 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' + }, { + prefetch => 'cds_very_very_very_long_relationship_name' + }); + + lives_and { + is $query->first->cds_very_very_very_long_relationship_name->first->cdid, 1 + } 'query with rel name over 30 chars and user condition survived and worked'; +} + # test join with row count ambiguity my $track = $schema->resultset('Track')->create({ trackid => 1, cd => 1, @@ -228,6 +253,8 @@ SKIP: { } } +done_testing; + # clean up our mess END { if($schema && ($dbh = $schema->storage->dbh)) { diff --git a/t/74mssql.t b/t/74mssql.t index 02e1950..04efcf6 100644 --- a/t/74mssql.t +++ b/t/74mssql.t @@ -31,13 +31,18 @@ for my $storage_type (@storage_types) { $schema = DBICTest::Schema->clone; - if ($storage_idx != 0) { # autodetect - $schema->storage_type("::$storage_type"); - } - $schema->connection($dsn, $user, $pass); - $schema->storage->ensure_connected; + if ($storage_idx != 0) { # autodetect + no warnings 'redefine'; + local *DBIx::Class::Storage::DBI::_typeless_placeholders_supported = + sub { 0 }; +# $schema->storage_type("::$storage_type"); + $schema->storage->ensure_connected; + } + else { + $schema->storage->ensure_connected; + } if ($storage_idx == 0 && ref($schema->storage) =~ /NoBindVars\z/) { my $tb = Test::More->builder; diff --git a/t/85utf8.t b/t/85utf8.t index b9993a1..399c46d 100644 --- a/t/85utf8.t +++ b/t/85utf8.t @@ -1,22 +1,27 @@ use strict; -use warnings; +use warnings; use Test::More; +use Test::Warn; use lib qw(t/lib); use DBICTest; +use utf8; -my $schema = DBICTest->init_schema(); - -if ($] <= 5.008000) { +warning_like (sub { - eval 'use Encode; 1' or plan skip_all => 'Need Encode run this test'; + package A::Comp; + use base 'DBIx::Class'; + sub store_column { shift->next::method (@_) }; + 1; -} else { + package A::Test; + use base 'DBIx::Class::Core'; + __PACKAGE__->load_components(qw(UTF8Columns +A::Comp)); + 1; +}, qr/Incorrect loading order of DBIx::Class::UTF8Columns/ ); - eval 'use utf8; 1' or plan skip_all => 'Need utf8 run this test'; -} -plan tests => 6; +my $schema = DBICTest->init_schema(); DBICTest::Schema::CD->load_components('UTF8Columns'); DBICTest::Schema::CD->utf8_columns('title'); @@ -26,12 +31,12 @@ my $cd = $schema->resultset('CD')->create( { artist => 1, title => 'øni', year my $utf8_char = 'uniuni'; -ok( _is_utf8( $cd->title ), 'got title with utf8 flag' ); -ok(! _is_utf8( $cd->year ), 'got year without utf8 flag' ); +ok( utf8::is_utf8( $cd->title ), 'got title with utf8 flag' ); +ok(! utf8::is_utf8( $cd->year ), 'got year without utf8 flag' ); -_force_utf8($utf8_char); +utf8::decode($utf8_char); $cd->title($utf8_char); -ok(! _is_utf8( $cd->{_column_data}{title} ), 'store utf8-less chars' ); +ok(! utf8::is_utf8( $cd->{_column_data}{title} ), 'store utf8-less chars' ); my $v_utf8 = "\x{219}"; @@ -47,24 +52,7 @@ ok( $cd->is_column_changed('title'), 'column is dirty after setting to something TODO: { local $TODO = 'There is currently no way to propagate aliases to inflate_result()'; $cd = $schema->resultset('CD')->find ({ title => $v_utf8 }, { select => 'title', as => 'name' }); - ok (_is_utf8( $cd->get_column ('name') ), 'utf8 flag propagates via as'); + ok (utf8::is_utf8( $cd->get_column ('name') ), 'utf8 flag propagates via as'); } - -sub _force_utf8 { - if ($] <= 5.008000) { - Encode::_utf8_on ($_[0]); - } - else { - utf8::decode ($_[0]); - } -} - -sub _is_utf8 { - if ($] <= 5.008000) { - return Encode::is_utf8 (shift); - } - else { - return utf8::is_utf8 (shift); - } -} +done_testing; diff --git a/t/86might_have.t b/t/86might_have.t index 8ebf7b8..a375404 100644 --- a/t/86might_have.t +++ b/t/86might_have.t @@ -2,6 +2,7 @@ use strict; use warnings; use Test::More; +use Test::Warn; use lib qw(t/lib); use DBICTest; @@ -11,8 +12,6 @@ my $queries; $schema->storage->debugcb( sub{ $queries++ } ); my $sdebug = $schema->storage->debug; -plan tests => 2; - my $cd = $schema->resultset("CD")->find(1); $cd->title('test'); @@ -40,4 +39,26 @@ $cd2->update; is($queries, 1, 'liner_notes (might_have) prefetched - do not load liner_notes on update'); +warning_like { + DBICTest::Schema::Bookmark->might_have( + linky => 'DBICTest::Schema::Link', + { "foreign.id" => "self.link" }, + ); +} + qr{"might_have/has_one" must not be on columns with is_nullable set to true}, + 'might_have should warn if the self.id column is nullable'; + +{ + local $ENV{DBIC_DONT_VALIDATE_RELS} = 1; + warning_is { + DBICTest::Schema::Bookmark->might_have( + slinky => 'DBICTest::Schema::Link', + { "foreign.id" => "self.link" }, + ); + } + undef, + 'Setting DBIC_DONT_VALIDATE_RELS suppresses nullable relation warnings'; +} + $schema->storage->debug($sdebug); +done_testing(); diff --git a/t/86sqlt.t b/t/86sqlt.t index 4327cef..e710ec1 100644 --- a/t/86sqlt.t +++ b/t/86sqlt.t @@ -14,6 +14,36 @@ BEGIN { my $schema = DBICTest->init_schema (no_deploy => 1); + +# Check deployment statements ctx sensitivity +{ + my $not_first_table_creation_re = qr/CREATE TABLE fourkeys_to_twokeys/; + + + my $statements = $schema->deployment_statements; + like ( + $statements, + $not_first_table_creation_re, + 'All create statements returned in 1 string in scalar ctx' + ); + + my @statements = $schema->deployment_statements; + cmp_ok (scalar @statements, '>', 1, 'Multiple statement lines in array ctx'); + + my $i = 0; + while ($i <= $#statements) { + last if $statements[$i] =~ $not_first_table_creation_re; + $i++; + } + + ok ( + ($i > 0) && ($i <= $#statements), + "Creation statement was found somewherere within array ($i)" + ); +} + + + # replace the sqlt calback with a custom version ading an index $schema->source('Track')->sqlt_deploy_callback(sub { my ($self, $sqlt_table) = @_; diff --git a/t/88result_set_column.t b/t/88result_set_column.t index c744121..615d8aa 100644 --- a/t/88result_set_column.t +++ b/t/88result_set_column.t @@ -9,7 +9,14 @@ use DBICTest; my $schema = DBICTest->init_schema(); -my $rs = $schema->resultset("CD")->search({}, { order_by => 'cdid' }); +my $rs = $schema->resultset("CD"); + +cmp_ok ( + $rs->count, + '!=', + $rs->search ({}, {columns => ['year'], distinct => 1})->count, + 'At least one year is the same in rs' +); my $rs_title = $rs->get_column('title'); my $rs_year = $rs->get_column('year'); @@ -36,6 +43,14 @@ warnings_exist (sub { is($rs_year->single, 1999, "single okay"); }, qr/Query returned more than one row/, 'single warned'); + +# test distinct propagation +is_deeply ( + [$rs->search ({}, { distinct => 1 })->get_column ('year')->all], + [$rs_year->func('distinct')], + 'distinct => 1 is passed through properly', +); + # test +select/+as for single column my $psrs = $schema->resultset('CD')->search({}, { diff --git a/t/99dbic_sqlt_parser.t b/t/99dbic_sqlt_parser.t index d4b1a9f..f8b88c3 100644 --- a/t/99dbic_sqlt_parser.t +++ b/t/99dbic_sqlt_parser.t @@ -1,7 +1,8 @@ -#!/usr/bin/perl use strict; use warnings; + use Test::More; +use Test::Exception; use lib qw(t/lib); use DBICTest; @@ -23,64 +24,103 @@ my @sources = grep ; { - my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { } } }); + my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { } } }); - foreach my $source (@sources) { - my $table = get_table($sqlt_schema, $schema, $source); + foreach my $source (@sources) { + my $table = get_table($sqlt_schema, $schema, $source); - my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints); - my @indices = $table->get_indices; - my $index_count = scalar(@indices); + my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints); + my @indices = $table->get_indices; + my $index_count = scalar(@indices); $index_count++ if ($source eq 'TwoKeys'); # TwoKeys has the index turned off on the rel def - is($index_count, $fk_count, "correct number of indices for $source with no args"); - } + is($index_count, $fk_count, "correct number of indices for $source with no args"); + } } { - my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 1 } } }); + my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 1 } } }); - foreach my $source (@sources) { - my $table = get_table($sqlt_schema, $schema, $source); + foreach my $source (@sources) { + my $table = get_table($sqlt_schema, $schema, $source); - my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints); - my @indices = $table->get_indices; - my $index_count = scalar(@indices); + my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints); + my @indices = $table->get_indices; + my $index_count = scalar(@indices); $index_count++ if ($source eq 'TwoKeys'); # TwoKeys has the index turned off on the rel def - is($index_count, $fk_count, "correct number of indices for $source with add_fk_index => 1"); - } + is($index_count, $fk_count, "correct number of indices for $source with add_fk_index => 1"); + } +} + +{ + my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 0 } } }); + + foreach my $source (@sources) { + my $table = get_table($sqlt_schema, $schema, $source); + + my @indices = $table->get_indices; + my $index_count = scalar(@indices); + is($index_count, 0, "correct number of indices for $source with add_fk_index => 0"); + } } { - my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 0 } } }); + { + package # hide from PAUSE + DBICTest::Schema::NoViewDefinition; - foreach my $source (@sources) { - my $table = get_table($sqlt_schema, $schema, $source); + use base qw/DBICTest::BaseResult/; - my @indices = $table->get_indices; - my $index_count = scalar(@indices); - is($index_count, 0, "correct number of indices for $source with add_fk_index => 0"); - } + __PACKAGE__->table_class('DBIx::Class::ResultSource::View'); + __PACKAGE__->table('noviewdefinition'); + + 1; + } + + my $schema_invalid_view = $schema->clone; + $schema_invalid_view->register_class('NoViewDefinition', 'DBICTest::Schema::NoViewDefinition'); + + throws_ok { create_schema({ schema => $schema_invalid_view }) } + qr/view noviewdefinition is missing a view_definition/, + 'parser detects views with a view_definition'; } +lives_ok (sub { + my $sqlt_schema = create_schema ({ + schema => $schema, + args => { + parser_args => { + sources => ['CD'] + }, + }, + }); + + is_deeply ( + [$sqlt_schema->get_tables ], + ['cd'], + 'sources limitng with relationships works', + ); + +}); + done_testing; sub create_schema { - my $args = shift; + my $args = shift; - my $schema = $args->{schema}; - my $additional_sqltargs = $args->{args} || {}; + my $schema = $args->{schema}; + my $additional_sqltargs = $args->{args} || {}; - my $sqltargs = { - add_drop_table => 1, - ignore_constraint_names => 1, - ignore_index_names => 1, - %{$additional_sqltargs} - }; + my $sqltargs = { + add_drop_table => 1, + ignore_constraint_names => 1, + ignore_index_names => 1, + %{$additional_sqltargs} + }; - my $sqlt = SQL::Translator->new( $sqltargs ); + my $sqlt = SQL::Translator->new( $sqltargs ); - $sqlt->parser('SQL::Translator::Parser::DBIx::Class'); - return $sqlt->translate({ data => $schema }) or die $sqlt->error; + $sqlt->parser('SQL::Translator::Parser::DBIx::Class'); + return $sqlt->translate({ data => $schema }) || die $sqlt->error; } sub get_table { diff --git a/t/lib/DBICTest/Schema/Artist.pm b/t/lib/DBICTest/Schema/Artist.pm index 4bc0b5c..dd5028e 100644 --- a/t/lib/DBICTest/Schema/Artist.pm +++ b/t/lib/DBICTest/Schema/Artist.pm @@ -44,6 +44,9 @@ __PACKAGE__->has_many( __PACKAGE__->has_many( cds_unordered => 'DBICTest::Schema::CD' ); +__PACKAGE__->has_many( + cds_very_very_very_long_relationship_name => 'DBICTest::Schema::CD' +); __PACKAGE__->has_many( twokeys => 'DBICTest::Schema::TwoKeys' ); __PACKAGE__->has_many( onekeys => 'DBICTest::Schema::OneKey' );