On Tue, Oct 27, 2009 at 12:50:15PM -0700, David E. Wheeler wrote:
> On Oct 26, 2009, at 1:59 PM, Tim Bunce wrote:
>
>>> It output nothing. When I uncommented that second-to-last line, it output
>>> "Set in STH". So it seems that a callback added to the dbh for a 
>>> statement
>>> method name does not end up getting passed on to the statement handle. So 
>>> I
>>> guess the Callbacks attribute is not passed on to statement handles 
>>> created
>>> for the database handle?
>>
>> Yeap.
>>
>>> Seems a shame…
>>
>> I recall thinking about it but opting not to. I can't recall why though.
>> Possibly just to err on the side of caution.
>>
>> I'm open to persuasion.
>
> I understand the caution, but it does seem inconsistent to me. I'd really 
> like to be able to set STH callbacks once in a DBH and be done with it, 
> just as a I can any other DBH attributes that also apply to STHs.

The "simplest thing that could possibly work" is probably:

    $dbh->{Callbacks} = {
        method_foo => sub { ... },
        method_bar => sub { ... },
        ChildCallbacks => {
            method_foo => sub { ... },
            method_bar => sub { ... },
        }
    }

so the code that creates a child handle can just check for
ChildCallbacks on the parent handle and set those as the Callbacks for
the new handle.

>>> One other thing: It's nice that the callbacks execute before the method
>>> call, so that you can disable it by undefing $_. But it'd be equally 
>>> handle
>>> to have callbacks after the method call.
>>
>> Yes, I'd always planned for that but never had a need to implement it 
>> myself.
>> I'd figured the callback code ref could, optionally, be an array ref and
>> if so, the second element would be the callback to call on method return.
>
> Yes, I saw that in our 
> [exchange](http://markmail.org/message/m2lr3n74nluh52jn) in 2005. What 
> would it take to make this happen?

Let's get Callbacks and ChildCallbacks documented first.

>> Note that a post-prepare() callback could be used to implement
>> inheritance of Callbacks.
>>
>> (If the method attributes of prepare($sql, \%attr) were applied to the
>> newly created sth, in the same way as connect(), then a pre-prepare
>> callback could be used to pass down the $dbh->{Callbacks} to the $sth.)
>
> Oh, that kind of callback. I'd love to see attributes to prepare passed on 
> to the sth. It makes perfect sense to me. I note that you thought in 2005 
> that it wouldn't happen before DBI2, but since that's probably another 5 
> years away at this rate…

[Tangental observation: NYTProf v3 is nearly done... :-]

ChildCallbacks seems like the way to go. Simple, effective, and easy to
implement.

>>> For example, I'd love to be able
>>> to create a callback on a statement handle to convert a timestamp column 
>>> to
>>> a DateTime object:
>>>
>>>    $sth->{PostCallbacks}{fetch} = sub {
>>>        my ($sth, $row) = @_;
>>>        $row->[3] = DateTime::Format::Pg->parse_datetime($row->[3]);
>>>    };
>>
>> That particular example is potentially risky - or at least unportable.
>> What if the driver doesn't implement a higher-level method (like
>> fetchrow_hashref or fetchall_* etc) in terms of fetch?
>
> Is that not a problem for any callback on any driver-implemented method?

Yes, it's one of the two fundamental problems with the Callback approach.
The other being that multiple callbacks for the same method can't
co-exist on the same handle.  Both need to be documented.

>> I've always felt the best way to do that kind of thing would be to tie()
>> the individual elements of the row buffer array - but I've never needed
>> to do it myself so never explored it.
>
> Well, and tie is pretty hackish IMHO.

The implementation (and performance) may not be great, but the concept
of tie (values vs containers) is good. Overloading would also work.
Either way it should be wrapped up in a nice interface.

> But let me cite another example. In our prior discussions, you 
> [wrote](http://markmail.org/message/dgnpmnzkk4g532lc):
>
>> Note that sth callbacks, for example, give you a relatively clean way to 
>> set the UTF8 flag on fetched data.

It seemed like a good idea at the time but I recant that now.
It's workable only for someone who knows what driver and methods will be
used to fetch data from a given handle. So it's just about okay for
in-house use but I couldn't recommend it as a general solution.

> Given that callbacks execute before data is fetched, how, exactly, could 
> this be done with the current implementation? I would exepct something like 
> this (using post-callbacks):
>
>    $sth->{PostCallbacks}{fetch} = sub {
>        my ($sth, $row) = @_;
>        Encode::_utf8_on $_ for @$row;
>        return;
>    };
>
> But that obviously won't work.

It would need post-call callbacks. How they get called would depend on
what works most efficiently and reasonably elegantly. I'd guess that the
values being returned would be in @_ (as aliases) and $_ would contain
the handle.

>> Another issue to keep in mind is that there's only one callback per 
>> method.
>> In other words there's no way for multiple callbacks for a single
>> method to coexist on a handle. That limits the usefulness of Callbacks
>> in general.
>
> Maybe. I don't think it's like JavaScript where you have any number of 
> libraries all adding their callbacks to global DOM elements. We have 
> lexical handles that can be modified only in scope. I don't think there's 
> much need to worry about supporting multiple callbacks until someone needs 
> them. And even then I'd suggest:
>
>     if (my $cb = $sth->{Callbacks}{fetch}) {
>         $sth->{Callbacks}{fetch} = sub {
>             $cb->(@_);
>              ... #other stuff ...
>         }
>     } else {
>         $sth->{Callbacks}{fetch} = sub {
>              ... #other stuff ...
>         }
>     }
>
> I dont think additional infrastructure is needed until the cowpaths demand it.

Lots of people have published DBIx::* modules and I'd guess many could
think of interesting uses for Callbacks. End users couldn't use more
than one such module at a time if they want to add callbacks on the same
methods.

On the other hand I'm happy to await the cowpaths.

>> Ideally I'd like to see an abstract interface for managing callbacks,
>> rather than the current "stuff it in a hash". That way future support
>> for post-method callbacks and multi-callbacks per method and handle
>> could be added without exposing (and locking us into) particular
>> implementation details.
>
> Do you have something in mind? What would it look like?

My suggestion (in http://markmail.org/message/dgnpmnzkk4g532lc) in 2005
still seems reaonable:

 $h->callback_add( $method_name, $code_ref [, $pre_or_post ]);
 $h->callback_delete( $method_name, $code_ref [, $pre_or_post ]);

except that $h->{Callbacks} = { name => $code, ... } is great for
setting up multiple callbacks in one go. So perhaps we'd have:

 $h->add_callbacks( { name => $code, ... } );

and make $h->{Callbacks} = ... automatically fire the add_callbacks
method. We'd still need a way to express pre-vs-post-call, so perhaps
allow

 $h->add_callbacks( { name => [ $pre_code, $post_code ], ... } );

In which case we've ended up back at the current (proposed) data
structure but with a method to provide some abstraction.
In which case adding the method can wait until we need to support
multiple callbacks :)

Tim.

Reply via email to