DBIx::Class::InflateColumn bug (was Re: [Dbix-class] RFC: Inflating user-supplied values)

Christopher H. Laco claco at chrislaco.com
Mon May 7 23:06:46 GMT 2007


Jason Kohles wrote:
> On May 7, 2007, at 1:16 PM, Christopher H. Laco wrote:
> =

>>>>> and while it is all working now (thanks Claco!) I have to admit I
>>>>> still find the behavior counter-intuitive at best.  I guess when I
>>>>> was just inflating DateTime objects it made more sense, since the
>>>>> inflated values were so much more complex than the representation
>>>>> that was stored in the database, but when I started using
>>>>> DBIx::Class::InflateColumn::Currency, it seemed odd to me that
>>>>> '$10.00' would be inflated to a Data::Currency object if it came from=
=3D
>>
>>>>> the database, but not if it came from the user.  I've fixed this for
>>>>> my immediate need by writing an HTML::FormFu::Inflator subclass that
>>>>> parallels DBIx::Class::InflateColumn::Currency, but going forward I'd=
=3D
>>
>>>>> like to avoid repeating so much code, so I've come up with an idea to=
=3D
>>
>>>>> extend DBIx::Class::InflateColumn to allow user-provided values to be=
=3D
>>
>>>>> inflated the same way that database values are.
>>>>
>>>> Well, if you call $obj->inflated_field('$10.00');
>>>>
>>>> then do $obj->inflated_field; the second call will return the inflated=
=3D
>>
>>>> object.
>>>>
>>>> I'm not sure I see what the problem you're trying to solve is?
>>
>> [mst]
>>
>>>> Well, if you call $obj->inflated_field('$10.00');
>>
>> The problem is, that will never work. I could be wrong, but whatever you
>> pass to inflated columns has to either be an inflated object, or a valid
>> value matching EXACTLY what the current DB supports.
>>
>> In this case '$10.00' is neither a currency object, nor a valid value
>> for a db column of type float.
>>
>> The same has always been true for DateTime fields (at least in my
>> experience):
>>
>> $obj->col('1-2-2007')
>>
>> will never work in database that don't grok that datetime format. But if
>> you pass in a DateTime object, all is well...
>>
> =

> The problem is that it does sometimes work, and I think I've finally
> figured out why...
> =

> If you set the value with $obj->col( '$10.00' ), and then request the
> value with $obj->col, you do indeed get back an inflated value.  Looking
> through the code however, it appears that the inflation is done by
> DBIx::Column::InflateColumn::get_inflated_column, meaning the value is
> inflated when you retrieve it, not when you set it.
> =

> What this means is that this will work as expected:
> =

> $obj->col( '$10.00' );
> print ref( $obj->col )."\n"; # will print Data::Currency

But this is very very dangerous. There is no way that the database will
save '$10.00' into a field marked as float...or decimal..or
double...maybe money type..but that's mssql jiggery...

> =

> However, this will _not_ work as expected:
> =

> $obj->col( '$10.00' );
> $obj->update;
> =

> The reason the second one doesn't work has to do with the inner workings
> of get_inflated_column and set_inflated_column.

See above. This should NEVER work...just like $obj->col('$ab.cd') should
never work...just like Data::Currency->new('$ab.cd') should also never
work. Regardless of any inflate inconsistancy, '$10.00' will never get
saved to the database correctly.

The fact that it gets that far is simply a bug in Data::Currency, one
that I shall correct shortly.

> =

> When you do this:  $obj->get_inflated_column( $col );
> =

> The get_inflated_column method will look for a previously inflated value
> in $self->{ _inflated_column }{ $col } and return it if it finds it, if
> it doesn't already have an inflated value, it will call get_column to
> retrieve the value from the database, inflate that, store it in $self->{
> _inflated_column }{ $col } and return the inflated value.
> =

> When you do this: $obj->set_inflated_column( $col, Data::Currency->new(
> 10, 'USD' ) );
> =

> The set_inflated_column method will deflate the object and call
> set_column with that value, so that other things that don't understand
> the inflated values will work correctly (things like
> DBIx::Class::Row::update for example).
> =

> When you do this: $obj->set_inflated_column( $col, '$10.00' ); is where
> things start to fall apart...

As they should...see above.

> =

> set_inflated_column will call _deflated_column with an argument of
> '$10.00', which gets returned unchanged because it isn't a reference,
> then it calls set_column with that value, thereby populating the object
> with invalid data.  Now when you call $obj->update, update will call
> get_dirty_columns and pass the results to SQL::Abstract::update, but
> since the value was not inflated before it was deflated, the unchanged
> value is passed to the database and the update proceeds with invalid data.
> =

> =

> I've been trying to determine the best way to fix this, but writing
> tests for it has proven difficult, I think I'm going to have to write a
> test class to inflate objects to...
> =


Again, mst will have to shed some sanity on myself...but I don't think
anything really is broken. If I pass an inflated object to a $column, it
gets deflated for UPDATE call. If I pass a value, it is left untouched
for an UPDATE call. Calling $column in either case finally returns an
inflated object.

The real problem here is, you're trying (and I'm mistakenly allowing
you) to try and use '$10.00' instead of '10.00' or 10.00 to create a new
Data::Currency object.

-=3DChris

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 187 bytes
Desc: OpenPGP digital signature
Url : http://lists.scsys.co.uk/pipermail/dbix-class/attachments/20070507/ea=
f1820e/signature.pgp


More information about the Dbix-class mailing list