From: Rafael Kitover Date: Fri, 12 Jun 2009 00:05:30 +0000 (+0000) Subject: Merge 'trunk' into 'sybase' X-Git-Tag: v0.08112~14^2~134 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=bb3e90c8291b0927ae3e39f29c01c5911374b3e3;hp=bbdc039bb73622702cbaaa25890349e3f2664fbf;p=dbsrgits%2FDBIx-Class.git Merge 'trunk' into 'sybase' r5443@hlagh (orig r6597): ribasushi | 2009-06-10 05:48:39 -0700 Adjust changelog r5446@hlagh (orig r6600): ribasushi | 2009-06-10 06:50:43 -0700 Release 0.08104 r5467@hlagh (orig r6614): ribasushi | 2009-06-11 05:29:48 -0700 Move around inflation tests r5468@hlagh (orig r6615): ribasushi | 2009-06-11 05:32:07 -0700 explicitly remove manifest on author mode make r5469@hlagh (orig r6616): ribasushi | 2009-06-11 06:02:41 -0700 IC::DT changes: Switch SQLite storage to DT::F::SQLite Fix exception when undef_if_invalid and timezone are both set on a column Split t/89inflate_datetime into separate tests Adjust makefile author dependencies r5470@hlagh (orig r6617): ribasushi | 2009-06-11 06:07:41 -0700 Move file_column test to inflate/ too r5472@hlagh (orig r6620): ribasushi | 2009-06-11 07:16:20 -0700 r5713@Thesaurus (orig r5712): ribasushi | 2009-03-08 23:53:28 +0100 Branch for datatype-aware updates r6604@Thesaurus (orig r6603): ribasushi | 2009-06-10 18:08:25 +0200 Test for type-aware update r6607@Thesaurus (orig r6606): ribasushi | 2009-06-10 19:57:04 +0200 Datatype aware update works r6609@Thesaurus (orig r6608): ribasushi | 2009-06-10 20:06:40 +0200 Whoops r6614@Thesaurus (orig r6613): ribasushi | 2009-06-11 09:23:54 +0200 Add attribute doc r6620@Thesaurus (orig r6619): ribasushi | 2009-06-11 16:15:53 +0200 Use equality, not comparison r5474@hlagh (orig r6622): ribasushi | 2009-06-11 07:21:53 -0700 Changes r5486@hlagh (orig r6635): ribasushi | 2009-06-11 12:29:10 -0700 Release 0.08105 r5487@hlagh (orig r6636): frew | 2009-06-11 13:02:11 -0700 Escape filename so that t\9... won't look like octal r5488@hlagh (orig r6637): ribasushi | 2009-06-11 13:09:55 -0700 Adjust tests for the DT::F::MySQL -> DT::F::SQLite change r5489@hlagh (orig r6638): arcanez | 2009-06-11 13:29:53 -0700 cookbook tweak for count distinct r5490@hlagh (orig r6639): ribasushi | 2009-06-11 14:39:12 -0700 Factor out as_query properly r5491@hlagh (orig r6640): ribasushi | 2009-06-11 14:50:41 -0700 Release 0.08106 r5493@hlagh (orig r6642): caelum | 2009-06-11 16:48:28 -0700 add support for DBICTEST_MSSQL_PERL5LIB to 74mssql.t --- diff --git a/Changes b/Changes index ab51a54..ade471a 100644 --- a/Changes +++ b/Changes @@ -1,16 +1,41 @@ Revision history for DBIx::Class +0.08106 2009-06-11 21:42:00 (UTC) + - Switched SQLite storage driver to DateTime::Format::SQLite + (proper timezone handling) + - Fix more test problems + +0.08105 2009-06-11 19:04:00 (UTC) + - Update of numeric columns now properly uses != to determine + dirtyness instead of the usual eq + - Fixes to IC::DT tests + - Fixed exception when undef_if_invalid and timezone are both set on + an invalid datetime column + +0.08104 2009-06-10 13:38:00 (UTC) - order_by now can take \[$sql, @bind] as in order_by => { -desc => \['colA LIKE ?', 'somestring'] } - SQL::Abstract errors are now properly croak()ed with the correct trace - populate() now properly reports the dataset slice in case of an exception - - fixed corner case when populate() erroneously falls back to + - Fixed corner case when populate() erroneously falls back to create() - - work around braindead mysql when doing subquery counts on + - Work around braindead mysql when doing subquery counts on resultsets containing identically named columns from several tables + - Fixed m2m add_to_$rel to invoke find_or_create on the far + side of the relation, to avoid duplicates + - DBIC now properly handles empty inserts (invoking all default + values from the DB, normally via INSERT INTO tbl DEFAULT VALUES + - Fix find_or_new/create to stop returning random rows when + default value insert is requested (RT#28875) + - Make IC::DT extra warning state the column name too + - It is now possible to transparrently search() on columns + requiring DBI bind (i.e. PostgreSQL BLOB) + - as_query is now a Storage::DBI method, so custom cursors can + be seamlessly used + - Fix search_related regression introduced in 0.08103 0.08103 2009-05-26 19:50:00 (UTC) - Multiple $resultset -> count/update/delete fixes. Now any diff --git a/Makefile.PL b/Makefile.PL index ebdf8b0..a9ad2d4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -73,12 +73,17 @@ my %force_requires_if_author = ( 'Test::Memory::Cycle' => 0, 'Devel::Cycle' => 1.10, + # t/inflate/datetime*.t + # t/72.pg + # t/36datetime.t # t/60core.t + 'DateTime::Format::SQLite' => 0, 'DateTime::Format::MySQL' => 0, - - # t/89inflate_datetime.t 'DateTime::Format::Pg' => 0, + # t/96_is_deteministic_value.t + 'DateTime::Format::Strptime' => 0, + # t/72pg.t $ENV{DBICTEST_PG_DSN} ? ('Sys::SigAction'=> 0) @@ -92,7 +97,6 @@ my %force_requires_if_author = ( 'namespace::clean' => 0.11, 'Hash::Merge', => 0.11, - # t/96_is_deteministic_value.t # t/746sybase.t 'DateTime::Format::Strptime' => 0, ); @@ -113,12 +117,16 @@ EOW build_requires ($module => $force_requires_if_author{$module}); } + print "Regenerating README\n"; system('pod2text lib/DBIx/Class.pm > README'); -} -auto_provides; + if (-f 'MANIFEST') { + print "Removing MANIFEST\n"; + unlink 'MANIFEST'; + } +} -auto_install; +auto_install(); WriteAll(); diff --git a/lib/DBIx/Class.pm b/lib/DBIx/Class.pm index 3f8acd3..6b28058 100644 --- a/lib/DBIx/Class.pm +++ b/lib/DBIx/Class.pm @@ -24,7 +24,7 @@ sub component_base_class { 'DBIx::Class' } # i.e. first release of 0.XX *must* be 0.XX000. This avoids fBSD ports # brain damage and presumably various other packaging systems too -$VERSION = '0.08103'; +$VERSION = '0.08106'; $VERSION = eval $VERSION; # numify for warning-free dev releases diff --git a/lib/DBIx/Class/InflateColumn/DateTime.pm b/lib/DBIx/Class/InflateColumn/DateTime.pm index d006e55..5646768 100644 --- a/lib/DBIx/Class/InflateColumn/DateTime.pm +++ b/lib/DBIx/Class/InflateColumn/DateTime.pm @@ -148,9 +148,13 @@ sub register_column { { inflate => sub { my ($value, $obj) = @_; + my $dt = eval { $obj->_inflate_to_datetime( $value, \%info ) }; - $self->throw_exception ("Error while inflating ${value} for ${column} on ${self}: $@") - if $@ and not $undef_if_invalid; + if (my $err = $@ ) { + return undef if ($undef_if_invalid); + $self->throw_exception ("Error while inflating ${value} for ${column} on ${self}: $err"); + } + $dt->set_time_zone($timezone) if $timezone; $dt->set_locale($locale) if $locale; return $dt; diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 574a8e1..b13b05a 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -279,7 +279,7 @@ any of your aliases using either of these: my $count = $rs->count; # Equivalent SQL: - # SELECT COUNT( DISTINCT( me.name ) ) FROM artist me + # SELECT COUNT( * ) FROM (SELECT me.name FROM artist me GROUP BY me.name) count_subq: =head2 Grouping results diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 515e45f..c220dd9 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -1927,7 +1927,13 @@ B: This feature is still experimental. sub as_query { my $self = shift; - return $self->result_source->storage->as_query($self->_resolved_attrs); + + my $attrs = $self->_resolved_attrs_copy; + + my ($sqlbind, $bind_attrs) = $self->result_source->storage + ->_select_args_to_query ($attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs); + + return $sqlbind; } =head2 find_or_new diff --git a/lib/DBIx/Class/ResultSource.pm b/lib/DBIx/Class/ResultSource.pm index c8f7e8d..aa636a7 100644 --- a/lib/DBIx/Class/ResultSource.pm +++ b/lib/DBIx/Class/ResultSource.pm @@ -116,6 +116,18 @@ automatically set. This is used to determine which columns to empty when cloning objects using L. It is also used by L. +=item is_numeric + +Set this to a true or false value (not C) to explicitly specify +if this column contains numeric data. This controls how set_column +decides whether to consider a column dirty after an update: if +C is true a numeric comparison C<< != >> will take place +instead of the usual C + +If not specified the storage class will attempt to figure this out on +first access to the column, based on the column C. The +result will be cached in this attribute. + =item is_foreign_key Set this to a true value for a column that contains a key from a diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index c08c00d..b2eee57 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -769,8 +769,39 @@ sub set_column { my $old_value = $self->get_column($column); $self->store_column($column, $new_value); - $self->{_dirty_columns}{$column} = 1 - if (defined $old_value xor defined $new_value) || (defined $old_value && $old_value ne $new_value); + + my $dirty; + if (defined $old_value xor defined $new_value) { + $dirty = 1; + } + elsif (not defined $old_value) { # both undef + $dirty = 0; + } + elsif ($old_value eq $new_value) { + $dirty = 0; + } + else { # do a numeric comparison if datatype allows it + my $colinfo = $self->column_info ($column); + + # cache for speed + if (not defined $colinfo->{is_numeric}) { + $colinfo->{is_numeric} = + $self->result_source->schema->storage->is_datatype_numeric ($colinfo->{data_type}) + ? 1 + : 0 + ; + } + + if ($colinfo->{is_numeric}) { + $dirty = $old_value != $new_value; + } + else { + $dirty = 1; + } + } + + # sadly the update code just checks for keys, not for their value + $self->{_dirty_columns}{$column} = 1 if $dirty; # XXX clear out the relation cache for this column delete $self->{related_resultsets}{$column}; diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index c95195f..7b392cb 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -910,37 +910,6 @@ sub _prep_for_execute { return ($sql, \@bind); } -=head2 as_query - -=over 4 - -=item Arguments: $rs_attrs - -=item Return Value: \[ $sql, @bind ] - -=back - -Returns the SQL statement and bind vars that would result from the given -ResultSet attributes (does not actually run a query) - -=cut - -sub as_query { - my ($self, $rs_attr) = @_; - - my $sql_maker = $self->sql_maker; - local $sql_maker->{for}; - - # my ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset) = $self->_select_args(...); - my @args = $self->_select_args($rs_attr->{from}, $rs_attr->{select}, $rs_attr->{where}, $rs_attr); - - # my ($sql, $bind) = $self->_prep_for_execute($op, $bind, $ident, [ $select, $cond, $order, $rows, $offset ]); - my ($sql, $bind) = $self->_prep_for_execute( - @args[0 .. 2], - [ @args[4 .. $#args] ], - ); - return \[ "($sql)", @{ $bind || [] }]; -} sub _fix_bind_params { my ($self, @bind) = @_; @@ -1243,6 +1212,23 @@ sub _select { return $self->_execute($self->_select_args(@_)); } +sub _select_args_to_query { + my $self = shift; + + my $sql_maker = $self->sql_maker; + local $sql_maker->{for}; + + # my ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset) + # = $self->_select_args($ident, $select, $cond, $attrs); + my ($op, $bind, $ident, $bind_attrs, @args) = + $self->_select_args(@_); + + # my ($sql, $bind) = $self->_prep_for_execute($op, $bind, $ident, [ $select, $cond, $order, $rows, $offset ]); + my ($sql, $prepared_bind) = $self->_prep_for_execute($op, $bind, $ident, \@args); + + return \[ "($sql)", @{ $prepared_bind || [] }]; +} + sub _select_args { my ($self, $ident, $select, $condition, $attrs) = @_; @@ -1582,6 +1568,27 @@ sub bind_attribute_by_data_type { return; } +=head2 is_datatype_numeric + +Given a datatype from column_info, returns a boolean value indicating if +the current RDBMS considers it a numeric value. This controls how +L decides whether to mark the column as +dirty - when the datatype is deemed numeric a C<< != >> comparison will +be performed instead of the usual C. + +=cut + +sub is_datatype_numeric { + my ($self, $dt) = @_; + + return 0 unless $dt; + + return $dt =~ /^ (?: + numeric | int(?:eger)? | (?:tiny|small|medium|big)int | dec(?:imal)? | real | float | double (?: \s+ precision)? | (?:big)?serial + ) $/ix; +} + + =head2 create_ddl_dir (EXPERIMENTAL) =over 4 diff --git a/lib/DBIx/Class/Storage/DBI/SQLite.pm b/lib/DBIx/Class/Storage/DBI/SQLite.pm index 5e125c9..7fada30 100644 --- a/lib/DBIx/Class/Storage/DBI/SQLite.pm +++ b/lib/DBIx/Class/Storage/DBI/SQLite.pm @@ -45,6 +45,8 @@ sub backup return $backupfile; } +sub datetime_parser_type { return "DateTime::Format::SQLite"; } + 1; =head1 NAME diff --git a/t/36datetime.t b/t/36datetime.t index d0d6aef..380242d 100644 --- a/t/36datetime.t +++ b/t/36datetime.t @@ -5,9 +5,8 @@ use Test::More; use lib qw(t/lib); use DBICTest; -eval { require DateTime::Format::MySQL }; - -plan $@ ? ( skip_all => 'Requires DateTime::Format::MySQL' ) +eval { require DateTime::Format::SQLite }; +plan $@ ? ( skip_all => 'Requires DateTime::Format::SQLite' ) : ( tests => 3 ); my $schema = DBICTest->init_schema( @@ -24,8 +23,6 @@ is( my $parser = $schema->storage->datetime_parser(); -# We're currently expecting a MySQL parser. May change in future. -is($parser, 'DateTime::Format::MySQL', 'Got expected datetime_parser'); - +is($parser, 'DateTime::Format::SQLite', 'Got expected storage-set datetime_parser'); isa_ok($schema->storage, 'DBIx::Class::Storage::DBI::SQLite', 'storage'); diff --git a/t/60core.t b/t/60core.t index 629d49d..b3eda4a 100644 --- a/t/60core.t +++ b/t/60core.t @@ -11,7 +11,7 @@ my $schema = DBICTest->init_schema(); plan tests => 106; -eval { require DateTime::Format::MySQL }; +eval { require DateTime::Format::SQLite }; my $NO_DTFM = $@ ? 1 : 0; my @art = $schema->resultset("Artist")->search({ }, { order_by => 'name DESC'}); @@ -195,7 +195,7 @@ is( $schema->resultset("Track")->find(100)->title, 'Insert or Update - updated', # get_inflated_columns w/relation and accessor alias SKIP: { - skip "This test requires DateTime::Format::MySQL", 8 if $NO_DTFM; + skip "This test requires DateTime::Format::SQLite", 8 if $NO_DTFM; isa_ok($new->updated_date, 'DateTime', 'have inflated object via accessor'); my %tdata = $new->get_inflated_columns; @@ -389,7 +389,7 @@ lives_ok (sub { my $newlink = $newbook->link}, "stringify to false value doesn't # test get_inflated_columns with objects SKIP: { - skip "This test requires DateTime::Format::MySQL", 5 if $NO_DTFM; + skip "This test requires DateTime::Format::SQLite", 5 if $NO_DTFM; my $event = $schema->resultset('Event')->search->first; my %edata = $event->get_inflated_columns; is($edata{'id'}, $event->id, 'got id'); diff --git a/t/74mssql.t b/t/74mssql.t index 49f7967..72e6552 100644 --- a/t/74mssql.t +++ b/t/74mssql.t @@ -1,6 +1,13 @@ use strict; use warnings; +# use this if you keep a copy of DBD::Sybase linked to FreeTDS somewhere else +BEGIN { + if (my $lib_dirs = $ENV{DBICTEST_MSSQL_PERL5LIB}) { + unshift @INC, $_ for split /:/, $lib_dirs; + } +} + use Test::More; use lib qw(t/lib); use DBICTest; diff --git a/t/89inflate_datetime.t b/t/89inflate_datetime.t deleted file mode 100644 index d165c33..0000000 --- a/t/89inflate_datetime.t +++ /dev/null @@ -1,174 +0,0 @@ -use strict; -use warnings; - -use Test::More; -use lib qw(t/lib); -use DBICTest; - -{ - local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /extra \=\> .+? has been deprecated/ }; - DBICTest::Schema->load_classes('EventTZDeprecated'); - DBICTest::Schema->load_classes('EventTZPg'); -} - -my $schema = DBICTest->init_schema(); - -plan tests => 57; - -SKIP: { - eval { require DateTime::Format::MySQL }; - skip "Need DateTime::Format::MySQL for inflation tests", 50 if $@; - - -# inflation test -my $event = $schema->resultset("Event")->find(1); - -isa_ok($event->starts_at, 'DateTime', 'DateTime returned'); - -# klunky, but makes older Test::More installs happy -my $starts = $event->starts_at; -is("$starts", '2006-04-25T22:24:33', 'Correct date/time'); - -TODO: { - local $TODO = "We can't do this yet before 0.09" if DBIx::Class->VERSION < 0.09; - - ok(my $row = - $schema->resultset('Event')->search({ starts_at => $starts })->single); - is(eval { $row->id }, 1, 'DT in search'); - - ok($row = - $schema->resultset('Event')->search({ starts_at => { '>=' => $starts } })->single); - is(eval { $row->id }, 1, 'DT in search with condition'); -} - -# create using DateTime -my $created = $schema->resultset('Event')->create({ - starts_at => DateTime->new(year=>2006, month=>6, day=>18), - created_on => DateTime->new(year=>2006, month=>6, day=>23) -}); -my $created_start = $created->starts_at; - -isa_ok($created->starts_at, 'DateTime', 'DateTime returned'); -is("$created_start", '2006-06-18T00:00:00', 'Correct date/time'); - -## timestamp field -isa_ok($event->created_on, 'DateTime', 'DateTime returned'); - -## varchar fields -isa_ok($event->varchar_date, 'DateTime', 'DateTime returned'); -isa_ok($event->varchar_datetime, 'DateTime', 'DateTime returned'); - -## skip inflation field -isnt(ref($event->skip_inflation), 'DateTime', 'No DateTime returned for skip inflation column'); - -# klunky, but makes older Test::More installs happy -my $createo = $event->created_on; -is("$createo", '2006-06-22T21:00:05', 'Correct date/time'); - -my $created_cron = $created->created_on; - -isa_ok($created->created_on, 'DateTime', 'DateTime returned'); -is("$created_cron", '2006-06-23T00:00:00', 'Correct date/time'); - - -# Test "timezone" parameter - -foreach my $tbl (qw/EventTZ EventTZDeprecated/) { - my $event_tz = $schema->resultset($tbl)->create({ - starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ), - created_on => DateTime->new(year=>2006, month=>1, day=>31, - hour => 13, minute => 34, second => 56, time_zone => "America/New_York" ), - }); - - is ($event_tz->starts_at->day_name, "Montag", 'Locale de_DE loaded: day_name'); - is ($event_tz->starts_at->month_name, "Dezember", 'Locale de_DE loaded: month_name'); - is ($event_tz->created_on->day_name, "Tuesday", 'Default locale loaded: day_name'); - is ($event_tz->created_on->month_name, "January", 'Default locale loaded: month_name'); - - my $starts_at = $event_tz->starts_at; - is("$starts_at", '2007-12-31T00:00:00', 'Correct date/time using timezone'); - - my $created_on = $event_tz->created_on; - is("$created_on", '2006-01-31T12:34:56', 'Correct timestamp using timezone'); - is($event_tz->created_on->time_zone->name, "America/Chicago", "Correct timezone"); - - my $loaded_event = $schema->resultset($tbl)->find( $event_tz->id ); - - isa_ok($loaded_event->starts_at, 'DateTime', 'DateTime returned'); - $starts_at = $loaded_event->starts_at; - is("$starts_at", '2007-12-31T00:00:00', 'Loaded correct date/time using timezone'); - is($starts_at->time_zone->name, 'America/Chicago', 'Correct timezone'); - - isa_ok($loaded_event->created_on, 'DateTime', 'DateTime returned'); - $created_on = $loaded_event->created_on; - is("$created_on", '2006-01-31T12:34:56', 'Loaded correct timestamp using timezone'); - is($created_on->time_zone->name, 'America/Chicago', 'Correct timezone'); - - # Test floating timezone warning - # We expect one warning - SKIP: { - skip "ENV{DBIC_FLOATING_TZ_OK} was set, skipping", 1 if $ENV{DBIC_FLOATING_TZ_OK}; - local $SIG{__WARN__} = sub { - like( - shift, - qr/You're using a floating timezone, please see the documentation of DBIx::Class::InflateColumn::DateTime for an explanation/, - 'Floating timezone warning' - ); - }; - my $event_tz_floating = $schema->resultset($tbl)->create({ - starts_at => DateTime->new(year=>2007, month=>12, day=>31, ), - created_on => DateTime->new(year=>2006, month=>1, day=>31, - hour => 13, minute => 34, second => 56, ), - }); - delete $SIG{__WARN__}; - }; - - # This should fail to set - my $prev_str = "$created_on"; - $loaded_event->update({ created_on => '0000-00-00' }); - is("$created_on", $prev_str, "Don't update invalid dates"); - - my $invalid = $schema->resultset('Event')->create({ - starts_at => '0000-00-00', - created_on => $created_on - }); - - is( $invalid->get_column('starts_at'), '0000-00-00', "Invalid date stored" ); - is( $invalid->starts_at, undef, "Inflate to undef" ); - - $invalid->created_on('0000-00-00'); - $invalid->update; - - { - local $@; - eval { $invalid->created_on }; - like( $@, qr/invalid date format/i, "Invalid date format exception"); - } -} - -## varchar field using inflate_date => 1 -my $varchar_date = $event->varchar_date; -is("$varchar_date", '2006-07-23T00:00:00', 'Correct date/time'); - -## varchar field using inflate_datetime => 1 -my $varchar_datetime = $event->varchar_datetime; -is("$varchar_datetime", '2006-05-22T19:05:07', 'Correct date/time'); - -## skip inflation field -my $skip_inflation = $event->skip_inflation; -is ("$skip_inflation", '2006-04-21 18:04:06', 'Correct date/time'); - -} # Skip if no MySQL DT::Formatter - -SKIP: { - eval { require DateTime::Format::Pg }; - skip ('Need DateTime::Format::Pg for timestamp inflation tests', 3) if $@; - - my $event = $schema->resultset("EventTZPg")->find(1); - $event->update({created_on => '2009-01-15 17:00:00+00'}); - $event->discard_changes; - isa_ok($event->created_on, "DateTime") or diag $event->created_on; - is($event->created_on->time_zone->name, "America/Chicago", "Timezone changed"); - # Time zone difference -> -6hours - is($event->created_on->iso8601, "2009-01-15T11:00:00", "Time with TZ correct"); -} diff --git a/t/95sql_maker.t b/t/95sql_maker.t index d99d201..c4a65a2 100644 --- a/t/95sql_maker.t +++ b/t/95sql_maker.t @@ -51,6 +51,7 @@ my $sql_maker = $schema->storage->sql_maker; # Make sure the carp/croak override in SQLA works (via SQLAHacks) my $file = __FILE__; +$file = "\Q$file\E"; throws_ok (sub { $schema->resultset ('Artist')->search ({}, { order_by => { -asc => 'stuff', -desc => 'staff' } } )->as_query; }, qr/$file/, 'Exception correctly croak()ed'); diff --git a/t/68inflate.t b/t/inflate/core.t similarity index 89% rename from t/68inflate.t rename to t/inflate/core.t index 3aa428d..cb5bcaf 100644 --- a/t/68inflate.t +++ b/t/inflate/core.t @@ -10,15 +10,12 @@ my $schema = DBICTest->init_schema(); eval { require DateTime }; plan skip_all => "Need DateTime for inflation tests" if $@; -plan tests => 21; +plan tests => 22; -$schema->class('CD') -#DBICTest::Schema::CD -->inflate_column( 'year', +$schema->class('CD') ->inflate_column( 'year', { inflate => sub { DateTime->new( year => shift ) }, deflate => sub { shift->year } } ); -Class::C3->reinitialize; # inflation test my $cd = $schema->resultset("CD")->find(3); @@ -66,6 +63,16 @@ eval { $cd->set_inflated_column('year', \'year + 1') }; ok(!$@, 'set_inflated_column to "year + 1"'); $cd->update; +TODO: { + local $TODO = 'this was left in without a TODO - should it work?'; + + eval { + $cd->store_inflated_column('year', \'year + 1'); + is_deeply( $cd->year, \'year + 1', 'deflate ok' ); + }; + ok(!$@, 'store_inflated_column to "year + 1"'); +} + $cd = $schema->resultset("CD")->find(3); is( $cd->year->year, $before_year+1, 'deflate ok' ); @@ -104,9 +111,3 @@ my $copy = $cd->copy({ year => $now, title => "zemoose" }); isnt( $copy->year->year, $before_year, "copy" ); -# eval { $cd->store_inflated_column('year', \'year + 1') }; -# print STDERR "ERROR: $@" if($@); -# ok(!$@, 'store_inflated_column to "year + 1"'); - -# is_deeply( $cd->year, \'year + 1', 'deflate ok' ); - diff --git a/t/inflate/datetime.t b/t/inflate/datetime.t new file mode 100644 index 0000000..ae8fc3b --- /dev/null +++ b/t/inflate/datetime.t @@ -0,0 +1,76 @@ +use strict; +use warnings; + +use Test::More; +use lib qw(t/lib); +use DBICTest; + +my $schema = DBICTest->init_schema(); + +eval { require DateTime::Format::SQLite }; +plan $@ + ? ( skip_all => "Need DateTime::Format::SQLite for DT inflation tests" ) + : ( tests => 18 ) +; + +# inflation test +my $event = $schema->resultset("Event")->find(1); + +isa_ok($event->starts_at, 'DateTime', 'DateTime returned'); + +# klunky, but makes older Test::More installs happy +my $starts = $event->starts_at; +is("$starts", '2006-04-25T22:24:33', 'Correct date/time'); + +TODO: { + local $TODO = "We can't do this yet before 0.09" if DBIx::Class->VERSION < 0.09; + + ok(my $row = + $schema->resultset('Event')->search({ starts_at => $starts })->single); + is(eval { $row->id }, 1, 'DT in search'); + + ok($row = + $schema->resultset('Event')->search({ starts_at => { '>=' => $starts } })->single); + is(eval { $row->id }, 1, 'DT in search with condition'); +} + +# create using DateTime +my $created = $schema->resultset('Event')->create({ + starts_at => DateTime->new(year=>2006, month=>6, day=>18), + created_on => DateTime->new(year=>2006, month=>6, day=>23) +}); +my $created_start = $created->starts_at; + +isa_ok($created->starts_at, 'DateTime', 'DateTime returned'); +is("$created_start", '2006-06-18T00:00:00', 'Correct date/time'); + +## timestamp field +isa_ok($event->created_on, 'DateTime', 'DateTime returned'); + +## varchar fields +isa_ok($event->varchar_date, 'DateTime', 'DateTime returned'); +isa_ok($event->varchar_datetime, 'DateTime', 'DateTime returned'); + +## skip inflation field +isnt(ref($event->skip_inflation), 'DateTime', 'No DateTime returned for skip inflation column'); + +# klunky, but makes older Test::More installs happy +my $createo = $event->created_on; +is("$createo", '2006-06-22T21:00:05', 'Correct date/time'); + +my $created_cron = $created->created_on; + +isa_ok($created->created_on, 'DateTime', 'DateTime returned'); +is("$created_cron", '2006-06-23T00:00:00', 'Correct date/time'); + +## varchar field using inflate_date => 1 +my $varchar_date = $event->varchar_date; +is("$varchar_date", '2006-07-23T00:00:00', 'Correct date/time'); + +## varchar field using inflate_datetime => 1 +my $varchar_datetime = $event->varchar_datetime; +is("$varchar_datetime", '2006-05-22T19:05:07', 'Correct date/time'); + +## skip inflation field +my $skip_inflation = $event->skip_inflation; +is ("$skip_inflation", '2006-04-21 18:04:06', 'Correct date/time'); diff --git a/t/inflate/datetime_mysql.t b/t/inflate/datetime_mysql.t new file mode 100644 index 0000000..51368ad --- /dev/null +++ b/t/inflate/datetime_mysql.t @@ -0,0 +1,97 @@ +use strict; +use warnings; + +use Test::More; +use Test::Exception; +use lib qw(t/lib); +use DBICTest; +use DBICTest::Schema; + +{ + local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /extra \=\> .+? has been deprecated/ }; + DBICTest::Schema->load_classes('EventTZ'); + DBICTest::Schema->load_classes('EventTZDeprecated'); +} + +eval { require DateTime::Format::MySQL }; +plan $@ + ? ( skip_all => "Need DateTime::Format::MySQL for inflation tests") + : ( tests => 33 ) +; + +my $schema = DBICTest->init_schema(); + +# Test "timezone" parameter +foreach my $tbl (qw/EventTZ EventTZDeprecated/) { + my $event_tz = $schema->resultset($tbl)->create({ + starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ), + created_on => DateTime->new(year=>2006, month=>1, day=>31, + hour => 13, minute => 34, second => 56, time_zone => "America/New_York" ), + }); + + is ($event_tz->starts_at->day_name, "Montag", 'Locale de_DE loaded: day_name'); + is ($event_tz->starts_at->month_name, "Dezember", 'Locale de_DE loaded: month_name'); + is ($event_tz->created_on->day_name, "Tuesday", 'Default locale loaded: day_name'); + is ($event_tz->created_on->month_name, "January", 'Default locale loaded: month_name'); + + my $starts_at = $event_tz->starts_at; + is("$starts_at", '2007-12-31T00:00:00', 'Correct date/time using timezone'); + + my $created_on = $event_tz->created_on; + is("$created_on", '2006-01-31T12:34:56', 'Correct timestamp using timezone'); + is($event_tz->created_on->time_zone->name, "America/Chicago", "Correct timezone"); + + my $loaded_event = $schema->resultset($tbl)->find( $event_tz->id ); + + isa_ok($loaded_event->starts_at, 'DateTime', 'DateTime returned'); + $starts_at = $loaded_event->starts_at; + is("$starts_at", '2007-12-31T00:00:00', 'Loaded correct date/time using timezone'); + is($starts_at->time_zone->name, 'America/Chicago', 'Correct timezone'); + + isa_ok($loaded_event->created_on, 'DateTime', 'DateTime returned'); + $created_on = $loaded_event->created_on; + is("$created_on", '2006-01-31T12:34:56', 'Loaded correct timestamp using timezone'); + is($created_on->time_zone->name, 'America/Chicago', 'Correct timezone'); + + # Test floating timezone warning + # We expect one warning + SKIP: { + skip "ENV{DBIC_FLOATING_TZ_OK} was set, skipping", 1 if $ENV{DBIC_FLOATING_TZ_OK}; + local $SIG{__WARN__} = sub { + like( + shift, + qr/You're using a floating timezone, please see the documentation of DBIx::Class::InflateColumn::DateTime for an explanation/, + 'Floating timezone warning' + ); + }; + my $event_tz_floating = $schema->resultset($tbl)->create({ + starts_at => DateTime->new(year=>2007, month=>12, day=>31, ), + created_on => DateTime->new(year=>2006, month=>1, day=>31, + hour => 13, minute => 34, second => 56, ), + }); + delete $SIG{__WARN__}; + }; + + # This should fail to set + my $prev_str = "$created_on"; + $loaded_event->update({ created_on => '0000-00-00' }); + is("$created_on", $prev_str, "Don't update invalid dates"); +} + +# Test invalid DT +my $invalid = $schema->resultset('EventTZ')->create({ + starts_at => '0000-00-00', + created_on => DateTime->now, +}); + +is( $invalid->get_column('starts_at'), '0000-00-00', "Invalid date stored" ); +is( $invalid->starts_at, undef, "Inflate to undef" ); + +$invalid->created_on('0000-00-00'); +$invalid->update; + +throws_ok ( + sub { $invalid->created_on }, + qr/invalid date format/i, + "Invalid date format exception" +); diff --git a/t/inflate/datetime_pg.t b/t/inflate/datetime_pg.t new file mode 100644 index 0000000..054edf1 --- /dev/null +++ b/t/inflate/datetime_pg.t @@ -0,0 +1,30 @@ +use strict; +use warnings; + +use Test::More; +use lib qw(t/lib); +use DBICTest; + +{ + local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /extra \=\> .+? has been deprecated/ }; + DBICTest::Schema->load_classes('EventTZPg'); +} + +eval { require DateTime::Format::Pg }; +plan $@ + ? ( skip_all => 'Need DateTime::Format::Pg for timestamp inflation tests') + : ( tests => 3 ) +; + + +my $schema = DBICTest->init_schema(); + +{ + my $event = $schema->resultset("EventTZPg")->find(1); + $event->update({created_on => '2009-01-15 17:00:00+00'}); + $event->discard_changes; + isa_ok($event->created_on, "DateTime") or diag $event->created_on; + is($event->created_on->time_zone->name, "America/Chicago", "Timezone changed"); + # Time zone difference -> -6hours + is($event->created_on->iso8601, "2009-01-15T11:00:00", "Time with TZ correct"); +} diff --git a/t/96file_column.t b/t/inflate/file_column.t similarity index 96% rename from t/96file_column.t rename to t/inflate/file_column.t index f4a166f..a9a75f0 100644 --- a/t/96file_column.t +++ b/t/inflate/file_column.t @@ -13,8 +13,8 @@ my $schema = DBICTest->init_schema(); plan tests => 10; my $rs = $schema->resultset('FileColumn'); -my $fname = '96file_column.t'; -my $source_file = file('t', $fname); +my $source_file = file(__FILE__); +my $fname = $source_file->basename; my $fh = $source_file->open('r') or die "failed to open $source_file: $!\n"; my $fc = eval { $rs->create({ file => { handle => $fh, filename => $fname } }) diff --git a/t/68inflate_resultclass_hashrefinflator.t b/t/inflate/hri.t similarity index 100% rename from t/68inflate_resultclass_hashrefinflator.t rename to t/inflate/hri.t diff --git a/t/68inflate_serialize.t b/t/inflate/serialize.t similarity index 100% rename from t/68inflate_serialize.t rename to t/inflate/serialize.t diff --git a/t/lib/DBICTest/Schema/Event.pm b/t/lib/DBICTest/Schema/Event.pm index caecdc1..c56c1bd 100644 --- a/t/lib/DBICTest/Schema/Event.pm +++ b/t/lib/DBICTest/Schema/Event.pm @@ -10,7 +10,7 @@ __PACKAGE__->table('event'); __PACKAGE__->add_columns( id => { data_type => 'integer', is_auto_increment => 1 }, - starts_at => { data_type => 'datetime', datetime_undef_if_invalid => 1 }, + starts_at => { data_type => 'datetime' }, created_on => { data_type => 'timestamp' }, varchar_date => { data_type => 'varchar', inflate_date => 1, size => 20, is_nullable => 1 }, varchar_datetime => { data_type => 'varchar', inflate_datetime => 1, size => 20, is_nullable => 1 }, diff --git a/t/lib/DBICTest/Schema/EventTZ.pm b/t/lib/DBICTest/Schema/EventTZ.pm index 321b279..2d8df28 100644 --- a/t/lib/DBICTest/Schema/EventTZ.pm +++ b/t/lib/DBICTest/Schema/EventTZ.pm @@ -10,10 +10,15 @@ __PACKAGE__->table('event'); __PACKAGE__->add_columns( id => { data_type => 'integer', is_auto_increment => 1 }, - starts_at => { data_type => 'datetime', timezone => "America/Chicago", locale => 'de_DE' }, + starts_at => { data_type => 'datetime', timezone => "America/Chicago", locale => 'de_DE', datetime_undef_if_invalid => 1 }, created_on => { data_type => 'timestamp', timezone => "America/Chicago", floating_tz_ok => 1 }, ); __PACKAGE__->set_primary_key('id'); +sub _datetime_parser { + require DateTime::Format::MySQL; + DateTime::Format::MySQL->new(); +} + 1; diff --git a/t/lib/DBICTest/Schema/EventTZDeprecated.pm b/t/lib/DBICTest/Schema/EventTZDeprecated.pm index 8a9a11d..a667976 100644 --- a/t/lib/DBICTest/Schema/EventTZDeprecated.pm +++ b/t/lib/DBICTest/Schema/EventTZDeprecated.pm @@ -16,4 +16,10 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key('id'); +sub _datetime_parser { + require DateTime::Format::MySQL; + DateTime::Format::MySQL->new(); +} + + 1; diff --git a/t/update/type_aware.t b/t/update/type_aware.t new file mode 100644 index 0000000..05f86c3 --- /dev/null +++ b/t/update/type_aware.t @@ -0,0 +1,27 @@ +use strict; +use warnings; + +use Test::More; +use lib qw(t/lib); +use DBICTest; + +my $schema = DBICTest->init_schema(); + +plan tests => 4; + +my $artist = $schema->resultset ('Artist')->first; +ok (!$artist->get_dirty_columns, 'Artist is clean' ); + +$artist->rank (13); +ok (!$artist->get_dirty_columns, 'Artist is clean after num value update' ); +$artist->discard_changes; + +$artist->rank ('13.00'); +ok (!$artist->get_dirty_columns, 'Artist is clean after string value update' ); +$artist->discard_changes; + +# override column info +$artist->result_source->column_info ('rank')->{is_numeric} = 0; +$artist->rank ('13.00'); +ok ($artist->get_dirty_columns, 'Artist is updated after is_numeric override' ); +$artist->discard_changes;