Re: Time to Document Callbacks
Tim Bunce wrote: On Sun, Mar 07, 2010 at 10:29:29AM -0800, David E. Wheeler wrote: On Mar 7, 2010, at 5:43 AM, Tim Bunce wrote: Looks good, thanks. Pity you removed the `$dbh-{private_myapp_sql_mode}` bit, though, as that's required when using Cconnect_cached(), which you almost certainly are doing if you need this hack. Are you sure it's required when using connected()? The connected method is only called for new connections. Yes, I just verified it with Bricolage, which uses connect_cached. connected() is called every time, whether or not a connection is a new connection. Uh, yeah, I just looked at the code. Sometimes I confuse myself. I think that's a bug. I always intended connected() to be used as an on-new-physical-connection-established hook. Any objections to making it so? Not from me. In fact if connect_cached called it every time I can imagine it would break some code I've seen. Looking at the code I can see an issue with clone(): it'll clone using the same method (connect/connect_cached) as the handle that's being cloned. I guess I can document that as a feature :) BTW, here's another issue I forgot to mention. I installed the DBI from svn and now get this error unless I rebuild each driver: [Sun Mar 07 10:22:24 2010] [error] DBI/DBD internal version mismatch (DBI is v95/s208, DBD ./mysql.xsi expected v94/s208) you probably need to rebuild the DBD driver (or possibly the DBI). I've never had an issue with binary compatibility between the DBI and a DBD. Did something change in this last build? Yes, the additional hook for sql_type_cast_svpv. But I shouldn't have bumped DBISTATE_VERSION for just that - the change was binary compatible with old drivers. (Drivers that care can use the DBIXS_REVISION macro to check if sql_type_cast_svpv is available at compile time and check it's non-zero to check it's available at runtime.) Fixed in r13837. Thanks. Tim. I used DBIXS_REVISION for those changes in DBD::Oracle and DBD::ODBC although the latter is not released yet. Martin -- Martin J. Evans Easysoft Limited http://www.easysoft.com
Re: Time to Document Callbacks
On Sat, Mar 06, 2010 at 04:07:09PM -0800, David E. Wheeler wrote: On Mar 6, 2010, at 3:45 PM, Tim Bunce wrote: I was tempted to leave The cool thing is but opted to drop it as it doesn't match the tone of the rest of the docs - although they are rather dry :) Pity. Thought I was starting to change that. ;-P :) I've made assorted edits and added some extra info. A sanity check would be most welcome. Looks good, thanks. Pity you removed the `$dbh-{private_myapp_sql_mode}` bit, though, as that's required when using Cconnect_cached(), which you almost certainly are doing if you need this hack. Are you sure it's required when using connected()? The connected method is only called for new connections. Tim.
Re: Time to Document Callbacks
On Mar 7, 2010, at 5:43 AM, Tim Bunce wrote: Looks good, thanks. Pity you removed the `$dbh-{private_myapp_sql_mode}` bit, though, as that's required when using Cconnect_cached(), which you almost certainly are doing if you need this hack. Are you sure it's required when using connected()? The connected method is only called for new connections. Yes, I just verified it with Bricolage, which uses connect_cached. connected() is called every time, whether or not a connection is a new connection. BTW, here's another issue I forgot to mention. I installed the DBI from svn and now get this error unless I rebuild each driver: [Sun Mar 07 10:22:24 2010] [error] DBI/DBD internal version mismatch (DBI is v95/s208, DBD ./mysql.xsi expected v94/s208) you probably need to rebuild the DBD driver (or possibly the DBI). I've never had an issue with binary compatibility between the DBI and a DBD. Did something change in this last build? Best, David
Re: Time to Document Callbacks
On Sun, Mar 07, 2010 at 10:29:29AM -0800, David E. Wheeler wrote: On Mar 7, 2010, at 5:43 AM, Tim Bunce wrote: Looks good, thanks. Pity you removed the `$dbh-{private_myapp_sql_mode}` bit, though, as that's required when using Cconnect_cached(), which you almost certainly are doing if you need this hack. Are you sure it's required when using connected()? The connected method is only called for new connections. Yes, I just verified it with Bricolage, which uses connect_cached. connected() is called every time, whether or not a connection is a new connection. Uh, yeah, I just looked at the code. Sometimes I confuse myself. I think that's a bug. I always intended connected() to be used as an on-new-physical-connection-established hook. Any objections to making it so? Looking at the code I can see an issue with clone(): it'll clone using the same method (connect/connect_cached) as the handle that's being cloned. I guess I can document that as a feature :) BTW, here's another issue I forgot to mention. I installed the DBI from svn and now get this error unless I rebuild each driver: [Sun Mar 07 10:22:24 2010] [error] DBI/DBD internal version mismatch (DBI is v95/s208, DBD ./mysql.xsi expected v94/s208) you probably need to rebuild the DBD driver (or possibly the DBI). I've never had an issue with binary compatibility between the DBI and a DBD. Did something change in this last build? Yes, the additional hook for sql_type_cast_svpv. But I shouldn't have bumped DBISTATE_VERSION for just that - the change was binary compatible with old drivers. (Drivers that care can use the DBIXS_REVISION macro to check if sql_type_cast_svpv is available at compile time and check it's non-zero to check it's available at runtime.) Fixed in r13837. Thanks. Tim.
Re: Time to Document Callbacks
On Mar 7, 2010, at 3:27 PM, Tim Bunce wrote: Uh, yeah, I just looked at the code. Sometimes I confuse myself. I think that's a bug. I always intended connected() to be used as an on-new-physical-connection-established hook. Any objections to making it so? Not from me, but you might get some bug reports. Looking at the code I can see an issue with clone(): it'll clone using the same method (connect/connect_cached) as the handle that's being cloned. I guess I can document that as a feature :) Never even noticed clone() before. But yeah, that sounds like a decent feature, as long as connect_cached does not return the exact same handle, eh? That is, clone() should always create a second, separate handle. Yes, the additional hook for sql_type_cast_svpv. But I shouldn't have bumped DBISTATE_VERSION for just that - the change was binary compatible with old drivers. (Drivers that care can use the DBIXS_REVISION macro to check if sql_type_cast_svpv is available at compile time and check it's non-zero to check it's available at runtime.) Dunno what sql_type_cast_svpv is for, but glad it's not just me. Fixed in r13837. Thanks. So there might be some folks around with the dev release who will have v95, even though the final will be v94? Best, David
Re: Time to Document Callbacks
On Tue, Mar 02, 2010 at 05:08:21PM -0800, David E. Wheeler wrote: Howdy, I've just committed r13835, which documents Callbacks. Yay! A few notes: Great. Thanks! * Tim, you earlier said: I could arrange for Callbacks to only apply to methods called by the applicationi, and not to 'nested calls'. That's a fairly major change after this length of time but given the limited use Callbacks have had, and the lack of documentation, it's not out of the question. The downside is that to intercept all fetched rows in a general way (like some kind of plugin utility module might want to do) you now have to add callbacks for 7 $sth fetch* methods and 6 $dbh select* methods. Is that really preferable? And I replied: Well, yes. But if there were 'fetch*' and 'select*' keys that could go to all of them at once, that would be cool, too. I don't know if you've changed anything here. Nope. No changes there. Even earlier, you wrote: If you're applying a callback to the fetch method and then your code calls fetchrow_hashref, for example, how do you know if the driver's fetchrow_hashref() method calls fetch() or fetchrow_arrayref()? The fetch and fetchrow_arrayref method are typically aliased by the driver, but they're two separate methods from the DBI dispatcher's point of view. Applying the same callback to both is a reasonable approach. I'm not sure what you decided to do about this, either. But I do think that whatever side-effects of such dispatch there are should be carefully documented as well. * I tried to include some useful examples, but one is DBD::Pg-specific and another is DBD::mysql-specific. Hope that's okay. * I didn't mention anything about the future possibility of post-method-call callbacks or an OO interface for setting up callbacks. I look forward to your edits and comments, and let me know if I can help with anything else for this release. I was tempted to leave The cool thing is but opted to drop it as it doesn't match the tone of the rest of the docs - although they are rather dry :) I've made assorted edits and added some extra info. A sanity check would be most welcome. Thanks again David! Tim.
Re: Time to Document Callbacks
On Mar 6, 2010, at 3:45 PM, Tim Bunce wrote: I was tempted to leave The cool thing is but opted to drop it as it doesn't match the tone of the rest of the docs - although they are rather dry :) Pity. Thought I was starting to change that. ;-P I've made assorted edits and added some extra info. A sanity check would be most welcome. Looks good, thanks. Pity you removed the `$dbh-{private_myapp_sql_mode}` bit, though, as that's required when using Cconnect_cached(), which you almost certainly are doing if you need this hack. Best, David
Re: Time to Document Callbacks
Howdy, I've just committed r13835, which documents Callbacks. Yay! A few notes: * Tim, you earlier said: I could arrange for Callbacks to only apply to methods called by the applicationi, and not to 'nested calls'. That's a fairly major change after this length of time but given the limited use Callbacks have had, and the lack of documentation, it's not out of the question. The downside is that to intercept all fetched rows in a general way (like some kind of plugin utility module might want to do) you now have to add callbacks for 7 $sth fetch* methods and 6 $dbh select* methods. Is that really preferable? And I replied: Well, yes. But if there were 'fetch*' and 'select*' keys that could go to all of them at once, that would be cool, too. I don't know if you've changed anything here. Even earlier, you wrote: If you're applying a callback to the fetch method and then your code calls fetchrow_hashref, for example, how do you know if the driver's fetchrow_hashref() method calls fetch() or fetchrow_arrayref()? The fetch and fetchrow_arrayref method are typically aliased by the driver, but they're two separate methods from the DBI dispatcher's point of view. Applying the same callback to both is a reasonable approach. I'm not sure what you decided to do about this, either. But I do think that whatever side-effects of such dispatch there are should be carefully documented as well. * I tried to include some useful examples, but one is DBD::Pg-specific and another is DBD::mysql-specific. Hope that's okay. * I didn't mention anything about the future possibility of post-method-call callbacks or an OO interface for setting up callbacks. I look forward to your edits and comments, and let me know if I can help with anything else for this release. Best, David
Re: Time to Document Callbacks
On Thu, Oct 29, 2009 at 05:10:07PM -0700, David E. Wheeler wrote: On Oct 29, 2009, at 4:24 PM, Tim Bunce wrote: I could arrange for Callbacks to only apply to methods called by the applicationi, and not to 'nested calls'. That's a fairly major change after this length of time but given the limited use Callbacks have had, and the lack of documentation, it's not out of the question. The downside is that to intercept all fetched rows in a general way (like some kind of plugin utility module might want to do) you now have to add callbacks for 7 $sth fetch* methods and 6 $dbh select* methods. Is that really preferable? Well, yes. Okay. Anyone else care to express an opinion? But if there were 'fetch*' and 'select*' keys that could go to all of them at once, that would be cool, too. Let's revisit that topic when/if post-call callbacks get supported. Tim.
Re: Time to Document Callbacks
On Tue, Oct 27, 2009 at 06:07:46PM -0700, David E. Wheeler wrote: On Oct 27, 2009, at 3:24 PM, Tim Bunce wrote: 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. That looks nice. Are STHs the only things that are children? DBHs are children of DRHs (but I try to avoid talking about DRHs). 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. Okay. Maybe it'll be done when I wake up in the morning? ;-P Here's a deal: you write some tests for ChildCallbacks in t/70callbacks.t and I'll implement them. 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... :-] Oh, nice. Does that mean you'll have more time for the DBI? That's the hope. ChildCallbacks seems like the way to go. Simple, effective, and easy to implement. Yeah, looks pretty nice. Would we also be able to pass them to prepare()? No. Much as I'd like to change prepare() to take method attributes like connect I'm nervous of making that change. I'd happily support someone else doing the leg work though. 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. Such as? Dunno. Would need some exploration and thought. 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. I'm starting to fail to see the point of callbacks aside from connect(). :-( I'm probably being over-cautious. Most drivers use fetch() or fetchrow_arrayref() as the lowest-level calling method used by the other fetch* ad select* methods. So applying the same callback to both would work find in most cases. 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. Hrm. That'd be inconsistent with the way precallbacks work. Yeah, well, it was just a guess off the top of my head :) I'd need to think about it and look at what could would be involved and what would be effcient. Tim.
Re: Time to Document Callbacks
On Oct 28, 2009, at 2:59 AM, Tim Bunce wrote: That looks nice. Are STHs the only things that are children? DBHs are children of DRHs (but I try to avoid talking about DRHs). Yes, let's pretend they don't exist here. Here's a deal: you write some tests for ChildCallbacks in t/70callbacks.t and I'll implement them. Deal. Oh, nice. Does that mean you'll have more time for the DBI? That's the hope. W00t. Yeah, looks pretty nice. Would we also be able to pass them to prepare()? No. Much as I'd like to change prepare() to take method attributes like connect I'm nervous of making that change. I'd happily support someone else doing the leg work though. Out of my league. I'm starting to fail to see the point of callbacks aside from connect(). :-( I'm probably being over-cautious. Most drivers use fetch() or fetchrow_arrayref() as the lowest-level calling method used by the other fetch* ad select* methods. So applying the same callback to both would work find in most cases. If I'm applying a callback to the fetch method, I expect it to execute when I actually call the fetch method on the STH to which I applied it. Reasonable, no? Is there some reason that wouldn't happen? If so, I'd call it a bug in the driver, frankly. Hrm. That'd be inconsistent with the way precallbacks work. Yeah, well, it was just a guess off the top of my head :) I'd need to think about it and look at what could would be involved and what would be effcient. I was thinking it would get the same arguments as the precallback, with an additional one that's a reference to the return value(s). Best, David
Re: Time to Document Callbacks
On Oct 28, 2009, at 10:26 AM, David E. Wheeler wrote: Here's a deal: you write some tests for ChildCallbacks in t/70callbacks.t and I'll implement them. Deal. Done in r13445. Best, David
Re: Time to Document Callbacks
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
Re: Time to Document Callbacks
On Sun, Oct 25, 2009 at 10:24:57PM -0700, David E. Wheeler wrote: On Oct 24, 2009, at 2:50 PM, Tim Bunce wrote: Callbacks are handled by the method dispatcher so all method names are valid (so don't bother trying to list them in the docs :) Plus the two special cases for connect_cached: 'connect_cached.new' and 'connect_cached.reused'. (There's also '*' but that's not really recommended.) Thanks. Following up on [this post](http://markmail.org/message/fus3dfauxs6yz6sv), I wrote this code: my $dbh = DBI-connect('dbi:Pg:dbname=bric', '', '', { Callbacks = { execute = sub { print Set in DBH\n; return; } } }); my $sth = $dbh-prepare('SELECT id FROM pref WHERE name = ?'); #$sth-{Callbacks}{execute} = sub { print Set in STH\n; return; }; $sth-execute('Time Zone'); 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. 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. 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.) 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? 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. 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. 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. Tim. p.s. An interesting use-case for Callbacks can be see in DBI::Gofer::Execute. See http://search.cpan.org/~timb/DBI-1.609/lib/DBI/Gofer/Execute.pm#DBI_GOFER_RANDOM The corresponding code is in the _install_rand_callbacks method in http://cpansearch.perl.org/src/TIMB/DBI-1.609/lib/DBI/Gofer/Execute.pm
Re: Time to Document Callbacks
On Oct 24, 2009, at 2:50 PM, Tim Bunce wrote: Callbacks are handled by the method dispatcher so all method names are valid (so don't bother trying to list them in the docs :) Plus the two special cases for connect_cached: 'connect_cached.new' and 'connect_cached.reused'. (There's also '*' but that's not really recommended.) Thanks! Tim. p.s. Might be worth skimming through the archives http://tinyurl.com/yl582mt Thanks. Following up on [this post](http://markmail.org/message/fus3dfauxs6yz6sv ), I wrote this code: my $dbh = DBI-connect('dbi:Pg:dbname=bric', '', '', { Callbacks = { execute = sub { print Set in DBH\n; return; } } }); my $sth = $dbh-prepare('SELECT id FROM pref WHERE name = ?'); #$sth-{Callbacks}{execute} = sub { print Set in STH\n; return; }; $sth-execute('Time Zone'); 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? Seems a shame… Best, David
Re: Time to Document Callbacks
On Oct 25, 2009, at 10:24 PM, David E. Wheeler 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? Seems a shame… 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. 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]); }; Is this something that's done at any level in the callbacks, or would it have to be added? Thoughts? Thanks, David
Re: Time to Document Callbacks
On Sat, Oct 24, 2009 at 08:45:42AM -0700, David E. Wheeler wrote: Tim, I think it's high time we documented callbacks. I'm pretty sure the DBIx::Class folks are going to want to make use of them shortly. I'd be happy to contribute the docs. Great! Is there a way to get a list of all possible callback keys? Callbacks are handled by the method dispatcher so all method names are valid (so don't bother trying to list them in the docs :) Plus the two special cases for connect_cached: 'connect_cached.new' and 'connect_cached.reused'. (There's also '*' but that's not really recommended.) Thanks! Tim. p.s. Might be worth skimming through the archives http://tinyurl.com/yl582mt