45 changes: 44 additions & 1 deletion lib/DBIx/Class/_Util.pm
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,15 @@ use DBIx::Class::Carp '^DBIx::Class|^DBICTest';

use Carp 'croak';
use Scalar::Util qw(weaken blessed reftype);
use List::Util qw(first);
use overload ();

use base 'Exporter';
our @EXPORT_OK = qw(sigwarn_silencer modver_gt_or_eq fail_on_internal_wantarray refcount hrefaddr is_exception);
our @EXPORT_OK = qw(
sigwarn_silencer modver_gt_or_eq fail_on_internal_wantarray
refcount hrefaddr is_exception
is_plain_value is_literal_value
);

sub sigwarn_silencer ($) {
my $pattern = shift;
Expand Down Expand Up @@ -153,6 +159,43 @@ sub modver_gt_or_eq ($$) {
eval { $mod->VERSION($ver) } ? 1 : 0;
}

sub is_literal_value ($) {
(
ref $_[0] eq 'SCALAR'
or
( ref $_[0] eq 'REF' and ref ${$_[0]} eq 'ARRAY' )
) ? 1 : 0;
}

# FIXME XSify - this can be done so much more efficiently
sub is_plain_value ($) {
no strict 'refs';
(
# plain scalar
(! length ref $_[0])
or
(
blessed $_[0]
and
# deliberately not using Devel::OverloadInfo - the checks we are
# intersted in are much more limited than the fullblown thing, and
# this is a relatively hot piece of code
(
# either has stringification which DBI prefers out of the box
#first { *{$_ . '::(""'}{CODE} } @{ mro::get_linear_isa( ref $_[0] ) }
overload::Method($_[0], '""')
or
# has nummification and fallback is *not* disabled
(
$_[1] = first { *{"${_}::(0+"}{CODE} } @{ mro::get_linear_isa( ref $_[0] ) }
and
( ! defined ${"$_[1]::()"} or ${"$_[1]::()"} )
)
)
)
) ? 1 : 0;
}

{
my $list_ctx_ok_stack_marker;

Expand Down
2 changes: 1 addition & 1 deletion t/100populate.t
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ warnings_like {
)
? ()
# one unique for populate() and create() each
: (qr/\QPOSSIBLE *PAST* DATA CORRUPTION detected \E.+\QTrigger condition encountered at @{[ __FILE__ ]} line\E \d/) x 2
: (qr/\QPOSSIBLE *PAST* DATA CORRUPTION detected \E.+\QTrigger condition encountered at @{[ __FILE__ ]} line\E \d/) x 3
], 'Data integrity warnings as planned';
lives_ok {
Expand Down
48 changes: 48 additions & 0 deletions t/inflate/datetime.t
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,52 @@ is("$varchar_datetime", '2006-05-22T19:05:07', 'Correct date/time');
my $skip_inflation = $event->skip_inflation;
is ("$skip_inflation", '2006-04-21 18:04:06', 'Correct date/time');

# create and update with literals
{
my $d = {
created_on => \ '2001-09-11',
starts_at => \[ '?' => '2001-10-26' ],
};

my $ev = $schema->resultset('Event')->create($d);

for my $col (qw(created_on starts_at)) {
ok (ref $ev->$col, "literal untouched in $col");
is_deeply( $ev->$col, $d->{$col});
is_deeply( $ev->get_inflated_column($col), $d->{$col});
is_deeply( $ev->get_column($col), $d->{$col});
}

$ev->discard_changes;

is_deeply(
{ $ev->get_dirty_columns },
{}
);

for my $col (qw(created_on starts_at)) {
isa_ok ($ev->$col, "DateTime", "$col properly inflated on retrieve");
}

for my $meth (qw(set_inflated_columns set_columns)) {

$ev->$meth({%$d});

is_deeply(
{ $ev->get_dirty_columns },
$d,
"Expected dirty cols after setting literals via $meth",
);

$ev->update;

for my $col (qw(created_on starts_at)) {
ok (ref $ev->$col, "literal untouched in $col updated via $meth");
is_deeply( $ev->$col, $d->{$col});
is_deeply( $ev->get_inflated_column($col), $d->{$col});
is_deeply( $ev->get_column($col), $d->{$col});
}
}
}

done_testing;
60 changes: 60 additions & 0 deletions t/internals/is_plain_value.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use warnings;
use strict;

use Test::More;
use Test::Warn;

use lib qw(t/lib);
use DBICTest;

use DBIx::Class::_Util 'is_plain_value';

{
package # hideee
DBICTest::SillyInt;

use overload
# *DELIBERATELY* unspecified
#fallback => 1,
'0+' => sub { ${$_[0]} },
;


package # hideee
DBICTest::SillyInt::Subclass;

our @ISA = 'DBICTest::SillyInt';


package # hideee
DBICTest::CrazyInt;

use overload
'0+' => sub { 666 },
'""' => sub { 999 },
fallback => 1,
;
}

# check DBI behavior when fed a stringifiable/nummifiable value
{
my $crazynum = bless {}, 'DBICTest::CrazyInt';
cmp_ok( $crazynum, '==', 666 );
cmp_ok( $crazynum, 'eq', 999 );

my $schema = DBICTest->init_schema( no_populate => 1 );
$schema->storage->dbh_do(sub {
$_[1]->do('INSERT INTO artist (name) VALUES (?)', {}, $crazynum );
});

is( $schema->resultset('Artist')->next->name, 999, 'DBI preferred stringified version' );
}

# make sure we recognize overloaded stuff properly
{
my $num = bless( \do { my $foo = 69 }, 'DBICTest::SillyInt::Subclass' );
ok( is_plain_value $num, 'parent-fallback-provided stringification detected' );
is("$num", 69, 'test overloaded object stringifies, without specified fallback');
}

done_testing;