Skip Menu |

This queue is for tickets about the DBIx-ProcedureCall CPAN distribution.

Report information
The Basics
Id: 66292
Status: stalled
Priority: 0/
Queue: DBIx-ProcedureCall

People
Owner: thilo [...] cpan.org
Requestors: bohica [...] ntlworld.com
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: (no value)
Fixed in: (no value)



Subject: Add support for DBD::ODBC
Hi Thilo, I'd like to add support for DBD::ODBC to DBIx::ProcedureCall however, the current way you identify the module to load to provide database specific support uses get_info(17) = database name. ODBC is not a database specific interface so database name could be "Microsoft SQL Server", "Microsoft Access", "Oracle", "MySQL", "Postgres" etc. I'm not going to write one for each database as ODBC provides a database independent way of calling procedures. I'm suggesting changing the logic in DBIx::ProcedureCall so that if no DBIx::ProcedureCall::XX can be found when get_info returns XX, it tries again with $dbh->{Driver}->{Name} which will return ODBC. This would allow me to provide an ODBC shim for DBIx::ProcedureCall. The only problem is that if someone uses Oracle under DBD::ODBC your get_info will still return Oracle which will load DBIx::ProcedureCall::Oracle and then do the wrong thing for more complex calls (e.g., try and use ORA_RSET attribute for a cursor). I'm not sure this is a big issue as most people going to Oracle will use DBD::Oracle and not DBD::ODBC but some mechanism of allowing someone to pick database name or DBI driver name would work. If you are happy with this I'll submit some patches for ODBC support but it might be better if you address the addition to pick the module via driver name after database name. Martin -- Martin J. Evans Wetherby, UK
Show quoted text
> I'm suggesting changing the logic in DBIx::ProcedureCall so that if no > DBIx::ProcedureCall::XX can be found when get_info returns XX, it tries > again with $dbh->{Driver}->{Name} which will return ODBC. This would > allow me to provide an ODBC shim for DBIx::ProcedureCall. > > The only problem is that if someone uses Oracle under DBD::ODBC your > get_info will still return Oracle which will load > DBIx::ProcedureCall::Oracle and then do the wrong thing
How about ignoring get_info altogether and just looking at $dbh->{Driver}->{Name}? If that works, you would be getting the ODBC shim even if connecting to Oracle via ODBC. I'm traveling right now, so cannot check, but do you know what the Driver->Name is as returned by DBD::Oracle and DBD::Postgres?
On Tue Mar 01 18:28:14 2011, THILO wrote: Show quoted text
> > I'm suggesting changing the logic in DBIx::ProcedureCall so that if no > > DBIx::ProcedureCall::XX can be found when get_info returns XX, it tries > > again with $dbh->{Driver}->{Name} which will return ODBC. This would > > allow me to provide an ODBC shim for DBIx::ProcedureCall. > > > > The only problem is that if someone uses Oracle under DBD::ODBC your > > get_info will still return Oracle which will load > > DBIx::ProcedureCall::Oracle and then do the wrong thing
> > How about ignoring get_info altogether and just looking at > $dbh->{Driver}->{Name}? If that works, you would be getting the ODBC > shim even if connecting to Oracle via ODBC. > I'm traveling right now, so cannot check, but do you know what the > Driver->Name is as returned by DBD::Oracle and DBD::Postgres?
You could get rid of the get_info and just use $dbh->{Driver}->{Name} but the code I attach does not do that - it simply falls back on the driver name if the get_info does not work. DBD::Pg reports "Pg" for the driver name so the Postgres.pm would need renaming to Pg.pm. BTW, I think the Postgres.pm was probably based on the Oracle.pm and the __close looks wrong to me in that it references DBD::Oracle::ORA_RSET. Some people won't even have DBD::Oracle. I do not use Mercurial and most of the files are additions anyway so I just tarred up the files I changed which are: README - mention ODBC ProcedureCall.pm - add {Driver}->{Name} logic New files: t/odbc.t - ProcedureCall/ODBC.pm Martin -- Martin J. Evans Wetherby, UK
Subject: patch.tgz
Download patch.tgz
application/x-gtar 7.1k

Message body not shown because it is not plain text.

Show quoted text
> You could get rid of the get_info and just use $dbh->{Driver}->{Name} > but the code I attach does not do that - it simply falls back on the > driver name if the get_info does not work.
I think I'll change the code to use just $dbh->{Driver}->{Name}. That seems cleaner. It also aligns the module names with the DBD names. Show quoted text
> DBD::Pg reports "Pg" for the > driver name so the Postgres.pm would need renaming to Pg.pm.
:-( Renaming the module should not be a problem, though... Show quoted text
> BTW, I think the Postgres.pm was probably based on the Oracle.pm and the > __close looks wrong to me in that it references DBD::Oracle::ORA_RSET. > Some people won't even have DBD::Oracle.
That does look like a bug. Show quoted text
> I do not use Mercurial and most of the files are additions anyway so I > just tarred up the files I changed
Thanks for the contribution. I committed the patches into a new branch "odbc" and will merge it into the next release. Is this urgent for you? I'd like to set up an ODBC test environment on my machine as well first. https://bitbucket.org/thiloplanz/dbix-procedurecall/changeset/83a713ea4fe9
On Wed Mar 02 10:20:19 2011, THILO wrote: Show quoted text
>
> > You could get rid of the get_info and just use $dbh->{Driver}->
{Name} Show quoted text
> > but the code I attach does not do that - it simply falls back on the > > driver name if the get_info does not work.
> > I think I'll change the code to use just $dbh->{Driver}->{Name}. > That seems cleaner. > It also aligns the module names with the DBD names.
Fair enough - I just didn't want to step on your toes. Show quoted text
>
> > DBD::Pg reports "Pg" for the > > driver name so the Postgres.pm would need renaming to Pg.pm.
> > :-( > Renaming the module should not be a problem, though... >
> > BTW, I think the Postgres.pm was probably based on the Oracle.pm and
the Show quoted text
> > __close looks wrong to me in that it references
DBD::Oracle::ORA_RSET. Show quoted text
> > Some people won't even have DBD::Oracle.
> > That does look like a bug. > >
> > I do not use Mercurial and most of the files are additions anyway so
I Show quoted text
> > just tarred up the files I changed
> > Thanks for the contribution. > I committed the patches into a new branch "odbc" and will merge it
into Show quoted text
> the next release. > Is this urgent for you? I'd like to set up an ODBC test environment on > my machine as well first.
No, it is not urgent at all. To be honest, I was really using it with DBD::Oracle but as I maintain DBD::ODBC I thought it would be nice to add ODBC support. To set up ODBC I'd get unixODBC (dev and bin packages, or build it yourself). The odbc.t is only testing to MS SQL Server which people mostly use freeTDS to access from non-Windows platforms. I didn't use freeTDS as my company writes its own MS SQL Server ODBC Driver. Show quoted text
procedurecall/changeset/83a713ea4fe9 There are two issues which I forgot to document in the ODBC.pm: 1. you should not call a procedure which issues a select right now as this adds the result-set to the statement handle the procedure was called on and DBIx::ProcedureCall has no way currently of returning it - as far as I could see. If someone was to use :fetch I could code it to fetch the result on the sth the procedure was run on but another problem is that a procedure can contain multiple selects. 2. output parameters are not available from most ODBC drivers until the procedure has finished and you usually have to call SQLMoreResults repeatedly until it returns SQL_NO_MORE_DATA. This should not really matter so long as you don't do (1). I may send you an amended ODBC.pm for this. Martin -- Martin J. Evans Wetherby, UK
Show quoted text
> > > BTW, I think the Postgres.pm was probably based on the Oracle.pm and
> the
> > > __close looks wrong to me in that it references
> DBD::Oracle::ORA_RSET.
> > > Some people won't even have DBD::Oracle.
> > > > That does look like a bug.
This is (un)dead code, it should never be called (and it would always fail). __close is used to close a refcursor (:cursor), which does not work with Postgresql anyway (they only have :table, and :fetch is built on top of table functions as well). I'll delete __close. Show quoted text
> There are two issues which I forgot to document in the ODBC.pm:
Yeah, it seems the way result sets are returned from the procedure is quite different from how Oracle does it. It would be nice if we could come up with a good spec for making :fetch work in a meaningful way. I'll read up on ODBC... What can you return from a stored procedure ? - a return value (for a function) - OUT parameters - one or more (!) result sets - potentially all of the above at the same time Show quoted text
> > 1. you should not call a procedure which issues a select right now as > this adds the result-set to the statement handle the procedure was > called on and DBIx::ProcedureCall has no way currently of returning it - > as far as I could see. > > If someone was to use :fetch I could code it to fetch the result on the > sth the procedure was run on but another problem is that a procedure can > contain multiple selects.
The preferred interface would be to offer :fetch for everything that returns a single result set. Returning the :fetch'd result set in addition to another return value of a procedure seems tricky, as do multiple result sets (for this we may need a :multi_fetch or :fetch[[[]]]). OUT parameters should work. If for complex cases :fetch does not work well, there could be other return strategies, just like we have :cursor for Oracle. In these modes, ProcedureCall just passes the statement from DBI back to the application and it has to figure out what to do with it. This is not the preferred way, but for really complex procedure with multiple result sets and return values it may be necessary. I'd just want :fetch to work for "normal" cases.
On Wed Mar 02 11:13:50 2011, THILO wrote: Show quoted text
> > There are two issues which I forgot to document in the ODBC.pm:
> > Yeah, it seems the way result sets are returned from the procedure is > quite different from how Oracle does it. > > It would be nice if we could come up with a good spec for making > :fetch work in a meaningful way. > > I'll read up on ODBC... > > What can you return from a stored procedure ? > - a return value (for a function)
there are no functions only procedures which return a value (your definition of a function) and procedures which don't return a value. Show quoted text
> - OUT parameters
yes Show quoted text
> - one or more (!) result sets
oh yes. It works like this: create procedure fred @outval integer OUT as begin set @outval = 2; select a from table_a; select b,c from table_b; end $s = $h->prepare(q/{call fred(?)}/); $s->bind_param_inout(1, \my $out, 100; $s->execute; # $out out param not set yet do { my $rs = $sth->fetchall_arrayref # do stuff here with $rs } while ($sth->{odbc_more_results}); # $out out param should be ok now The procedure can also contain inserts/updates/deletes but DBD::ODBC skips those in odbc_more_results when it spots there are no resultant columns. Show quoted text
> - potentially all of the above at the same time
yes Show quoted text
> > > > 1. you should not call a procedure which issues a select right now
as Show quoted text
> > this adds the result-set to the statement handle the procedure was > > called on and DBIx::ProcedureCall has no way currently of returning
it - Show quoted text
> > as far as I could see. > > > > If someone was to use :fetch I could code it to fetch the result on
the Show quoted text
> > sth the procedure was run on but another problem is that a procedure
can Show quoted text
> > contain multiple selects.
> > The preferred interface would be to offer :fetch for everything that > returns a single result set.
You don't know there is a single result-set. Show quoted text
> Returning the :fetch'd result set in addition to another return value
of Show quoted text
> a procedure seems tricky, as do multiple result sets (for this we may > need a :multi_fetch or :fetch[[[]]]). OUT parameters > should work.
Multiple result-sets could be catered with: while (my $rs = myproc:fetch(args); Show quoted text
> If for complex cases :fetch does not work well, there could be other > return strategies, just like we have :cursor for Oracle. In these
modes, Show quoted text
> ProcedureCall just passes the statement from DBI back to the
application Show quoted text
> and it has to figure out what to do with it. This is not the preferred > way, but for really complex procedure with multiple result sets and > return values it may be necessary.
Personally, I think "cursor" in Oracle is closer to how ODBC does it except instead of returning a NEW cursor you actually get the same sth the procedure was called on/ Show quoted text
> > I'd just want :fetch to work for "normal" cases.
ok but I'm not sure what a normal case is. Martin -- Martin J. Evans Wetherby, UK
Show quoted text
> there are no functions only procedures which return a value (your > definition of a function) and procedures which don't return a value.
Is that a different mechanism from OUT parameters? Show quoted text
> > The preferred interface would be to offer :fetch for everything that > > returns a single result set.
> > You don't know there is a single result-set.
Why not? If the procedure has a single select it will always return a single result set, no? The whole point of these :annotations is to give ProcedureCall some extra information (that cannot be determined by the module, but that the user knows) about how to handle the function. If you specify nothing, it will have to do some default behaviour (maybe just support OUT parameters), but for the sample you gave with the two result sets, I think I'd like to use say :fetch[[[]]] to get an array with two results, each of them an array of rows. For really complex cases (or large data that needs to be streamed), we'd have to return the statement handle directly and leave it to the application to handle it. Maybe call it :handle Show quoted text
> > I'd just want :fetch to work for "normal" cases.
> > ok but I'm not sure what a normal case is.
Naive definition of normal: a procedure that contains a single select and returns a single result set (of reasonably small size). For those, the same :fetch we have now should work well. Plus OUT parameters. Extended "normal" case (needs some new :fetch types): Returns multiple result sets, which can be turned into array-of-fetches. For those cases, it should not be necessary for application code to touch the statement handle.
On Wed Mar 02 18:41:48 2011, THILO wrote: Show quoted text
> > there are no functions only procedures which return a value (your > > definition of a function) and procedures which don't return a value.
> > Is that a different mechanism from OUT parameters?
no. {? call myproc(?)} bind_param_inout(1, \my $procreturn); bind_param_inout(1, \my $outparam); Show quoted text
> > > The preferred interface would be to offer :fetch for everything
that Show quoted text
> > > returns a single result set.
> > > > You don't know there is a single result-set.
> > Why not? > If the procedure has a single select it will always return a single > result set, no?
What I meant was you cannot examine the procedure via ODBC to know whether you are going to 0 or more result-sets. Show quoted text
> The whole point of these :annotations is to give ProcedureCall some > extra information (that cannot be determined by the module, but that
the Show quoted text
> user knows) about how to handle the function.
In that case, that is fine - if a user know it returns N result-sets. Show quoted text
> If you specify nothing, it will have to do some default behaviour > (maybe just support OUT parameters), but for the sample you gave with > the two result sets, I think I'd like to use say :fetch[[[]]] > to get an array with two results, each of them an array of rows.
You could do that but you'll have to change DBIx::ProcedureCall::DRIVER to return the prepared/executed sth and DBIx::ProcedureCall::__fetch and the callers to pass the original sth instead of the thing you get back as the function return value as the "fetch" at the moment seems to use the function return value. Also DBIx::ProcedureCall::__run_procedure would need changing to support fetch as a procedure which does not return a value (as in a function) can still produce result-sets. Show quoted text
> For really complex cases (or large data that needs to be streamed), > we'd have to return the statement handle directly and leave it to the > application to handle it.
yup Show quoted text
> Maybe call it :handle
ok Show quoted text
> > > I'd just want :fetch to work for "normal" cases.
> > > > ok but I'm not sure what a normal case is.
> > Naive definition of normal: a procedure that contains a single select > and returns a single result set (of reasonably small size). For those, > the same :fetch we have now should work well. Plus OUT parameters.
with the alterations I outline above. Show quoted text
> Extended "normal" case (needs some new :fetch types): Returns multiple > result sets, which can be turned into array-of-fetches.
ok Show quoted text
> For those cases, it should not be necessary for application code to > touch the statement handle. > >
agreed. Attached a new ODBC.pm and ProcedureCall.pm hacked (and it is a hack simply to demonstrate) to make a simple single select in a procedure work (it changes sub arguments so it will break Oracle.pm/Postgres.pm as it stands). Also I only tested it briefly using CLI which fails for procedure returning a result-set but not returning like a function because of: # any fetch implies function if ( grep /^fetch/, keys %attr ) { $attr{'function'} = 1; $attr{'fetch'} = 1; } in DBIx::ProcedureCall::run. Also, to properly support output parameters in procedures that may return result-sets the ODBC.pm would need to repeatedly call odbc_more_results. I didn't do anything about this because it depends on whether you are fetching or not and fetching from a procedure cannot happen right now. create procedure drop_me_4 @ival int as select @ival; perl -Iblib/lib -Iblib/arch -MDBIx::ProcedureCall::CLI -e 'function' 'drop_me_4:fetch[[]]' 1 executed function 'drop_me_4:fetch[[]]'. Show quoted text
------ result ----------- $VAR1 = [ [ '1' ] ]; 1 rows returned ------------------------ Martin -- Martin J. Evans Wetherby, UK
Subject: ODBC.pm
# Shamelessly copied from DBIx::ProcedureCall::Oracle and then support # for named parameters, boolean and cursors removed. package DBIx::ProcedureCall::ODBC; use strict; use warnings; use Carp qw(croak); our $VERSION = '0.10'; our $ORA22905; sub __run_procedure{ shift; my $dbh = shift; my $name = shift; my $attr = shift; my $params; # if there is one more arg and it is a hashref, then we use named parameters if (@_ == 1 and ref $_[0] eq 'HASH') { croak(__PACKAGE__ . " does not support named parameters"); } # otherwise they are positional parameters my $sql = "{call $name"; if (@_){ $sql .= '(' . join (',' , map ({ '?'} @_ )) . ')'; } $sql .= '}'; # prepare $sql = $attr->{cached} ? $dbh->prepare_cached($sql) : $dbh->prepare($sql); # bind DBIx::ProcedureCall::__bind_params($sql, 1, \@_); # execute $sql->execute; } sub __run_function{ shift; my $dbh = shift; my $name = shift; my $attr = shift; my $params; ##### # any fetch implies cursor (unless it is a table function) ##### if ($attr->{'fetch'}) { ##### croak(__PACKAGE__ . ' does not support the returning of a cursor'); ##### } # if there is one more arg and it is a hashref , then we use with named parameters if (@_ == 1 and ref $_[0] eq 'HASH') { croak(__PACKAGE__ . " does not support named parameters"); } # otherwise they are positional parameters my $sql; $sql = "{? = call $name"; if (@_){ $sql .= '(' . join (',' , map ({ '?'} @_ )) . ')'; } $sql .= '}'; # prepare $sql = $attr->{cached} ? $dbh->prepare_cached($sql) : $dbh->prepare($sql); # bind my $i = 1; my $r; $sql->bind_param_inout($i++, \$r, 100); DBIx::ProcedureCall::__bind_params($sql, $i, \@_); $sql->execute; return ($sql, $r); } sub __close{ shift; my $sth = shift; # there is nothing to close as ODBC does not support returning cursors # like DBD::Oracle does } 1; __END__ =head1 NAME DBIx::ProcedureCall::ODBC - ODBC driver for DBIx::ProcedureCall =head1 DESCRIPTION This is an internal module used by DBIx::ProcedureCall. You do not need to access it directly. However, you should read the following documentation, because it explains how to use DBIx::ProcedureCall with ODBC databases. =head2 Procedures and functions DBIx::ProcedureCall needs to know if you are about to call a function or a procedure (because the SQL is different). You have to make sure you call the wrapper subroutines in the right context (or you can optionally declare the correct type, see below) You have to call procedures in void context. # works dbms_random_initialize($conn, 12345); # fails print dbms_random_initialize($conn, 12345); You have to call functions in non-void context. # works print sysdate($conn); # fails sysdate($conn); If you try to call a function as a procedure, you will get a database error. If you do not want to rely on this mechanism, you can declare the correct type using the attributes :procedure and :function: use DBIx::ProcedureCall qw[ sysdate:function dbms_random.initialize:procedure ]; If you use these attributes, the calling context will be ignored and the call will be dispatched according to your declaration. NOTE: in ODBC there are no functions as such. Everything is a procedure but some procedures return a value and others do not. You should class ODBC procedures which return a value as "functions" in DBIx::ProcedureCall and ODBC procedures which do not return a value as "procedures". =head2 Returning result sets This package does not support returning result-sets like DBD::Oracle does with a reference cursor. =head2 Named Parameters This package does not support named parameters. =head1 SEE ALSO L<DBIx::ProcedureCall> for information about this module that is not ODBC-specific. L<DBD::ODBC> =head1 AUTHOR Martin J Evans, E<lt>mjevans@cpan.orgE<gt> =head1 COPYRIGHT AND LICENSE Copyright 2011 by Martin J. Evans This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut
Subject: ProcedureCall.pm

Message body is not shown because it is too large.