File Coverage

blib/lib/DBIx/Procedure/Oracle.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package DBIx::Procedure::Oracle;
2              
3             $VERSION = 0.2;
4              
5 1     1   2868 use DBI qw( :sql_types );
  1         18526  
  1         1436  
6             # sql_types are needed for binding dates and numbers
7             # ORA_NUMBER and ORA_DATE don't work but SQL_NUMERIC etc. do
8 1     1   2575 use DBD::Oracle qw( :ora_types );
  0            
  0            
9             # but we do need ora_types so that we can bind cursors as ORA_RSET
10              
11             sub new {
12             my ($self,$dbh,%resolve) = @_;
13             my $class = ref $self || $self;
14             my $usage = <<'EOF';
15             Usage: $proc = DBIx::Procedure::Oracle->new($dbh
16             ,[ owner => 'oracle_user' ]
17             ,[ package_name => 'oracle_package' ]
18             ,object_name => 'oracle_procedure'
19             );
20             EOF
21              
22             unless( UNIVERSAL::isa($dbh,'DBI::db') && $dbh->{Driver}->{Name} =~ /^Oracle$/i ){
23             die "$class - First argument must be a valid DBI handle for Oracle";
24             }
25             die "$class - No procedure name provided.\n$usage" unless exists $resolve{object_name};
26             my $this = bless {}, $class;
27             $this->{-args} = resolve_procedure( $dbh, \%resolve );
28             my $sql = generate_sql( \%resolve, $this->{-args} );
29             $this->{-sth} = $dbh->prepare($sql);
30             $this->{-sql} = $sql;
31             return $this;
32             }
33              
34             sub DESTROY { $_[0]->{-sth}->finish if exists $_[0]->{-sth} }
35              
36             sub resolve_procedure ($\%) {
37             my ($dbh,$resolve) = @_;
38             # if we don't provide a user we'll assume that its the current oracle user
39             # if we don't provide a package we'll assume that the procedure isn't in one
40             # In future: allow named argument syntax as well as positional arguments
41             my $sql =<<'EOF';
42             select argument_name, position, sequence, data_type, in_out
43             from all_arguments
44             where ( owner = :1 or ( :1 is null and owner = user ) )
45             and ( package_name = :2 or ( :2 is null and package_name is null ) )
46             and object_name = :3
47             order by position
48             EOF
49             my $sth = $dbh->prepare($sql);
50             $sth->bind_param(1, uc $resolve->{owner} );
51             $sth->bind_param(2, uc $resolve->{package_name} );
52             $sth->bind_param(3, uc $resolve->{object_name} );
53             $sth->execute;
54             my @rows;
55             while( my $row = $sth->fetchrow_hashref ){
56             if( $row->{DATA_TYPE} =~ /VARCHAR/ ){ # not CHAR
57             $row->{SQL_TYPE} = { sql_type => SQL_VARCHAR() };
58             }elsif( $row->{DATA_TYPE} =~ /NUMBER/ ){ # should handle all numbers
59             $row->{SQL_TYPE} = { sql_type => SQL_NUMERIC() };
60             }elsif( $row->{DATA_TYPE} =~ /CURSOR/ ){
61             $row->{SQL_TYPE} = { ora_type => ORA_RSET() }; # oracle cursors
62             }elsif( $row->{DATA_TYPE} =~ /CHAR/ ){ # but not VARCHAR (see above)
63             $row->{SQL_TYPE} = { sql_type => SQL_CHAR() };
64             }elsif( $row->{DATA_TYPE} =~ /DATE/
65             || $row->{DATA_TYPE} =~ /TIME/
66             || $row->{DATA_TYPE} =~ /INTERVAL/ ){
67             $row->{SQL_TYPE} = { sql_type => SQL_DATE() }; # types i have seen
68             }else{ # don't know. Try our best.
69             $row->{SQL_TYPE} = { sql_type => SQL_UNKNOWN_TYPE() }; # catch all
70             }
71             push @rows, $row;
72             }
73             $sth->finish;
74             return \@rows;
75             }
76              
77             sub generate_sql (\%\@) {
78             my($resolve, $args) = @_;
79             my $sql = "begin\n\t" ; # nicely formatted anonymous pl/sql block
80             my $start = 0;
81             if( @$args && $args->[0]->{POSITION} == 0 ){ # is a function!
82             $sql .= ':1 := ';
83             $start = 1;
84             }
85             $sql .= "$resolve->{owner}." if exists $resolve->{owner};
86             $sql .= "$resolve->{package_name}." if exists $resolve->{package_name};
87             $sql .= $resolve->{object_name};
88             if( @$args ){
89             $sql .= '('; # brackets only necessary when there are arguments
90             for( my $i = $start; $i < @$args; $i++ ){
91             $sql .= ":$args->[$i]->{SEQUENCE},";
92             }
93             $sql =~ s/,$//;
94             $sql .= ')';
95             }
96             $sql .= ";\nend;";
97             return $sql;
98             }
99              
100             sub execute {
101             my $this = $_[0];
102             my $sth = $this->{-sth};
103             my $retval;
104             my $start = 0;
105             my @args = @{ $this->{-args} };
106             if( $args[0]->{POSITION} == 0 ){ # function
107             $sth->bind_param_inout(1, \$retval, $args[0]->{SQL_TYPE} );
108             $start = 1;
109             }
110             for( my $i = $start; $i < @args; $i++ ){
111             if ( $args[$i]->{IN_OUT} eq 'IN' ){
112             $sth->bind_param( $args[$i]->{SEQUENCE}
113             ,$_[ $args[$i]->{POSITION} ]
114             ,$args[$i]->{SQL_TYPE}
115             );
116             }else{
117             $sth->bind_param_inout( $args[$i]->{SEQUENCE}
118             ,\$_[ $args[$i]->{POSITION} ]
119             ,$args[$i]->{SQL_TYPE}
120             );
121             }
122             }
123             $sth->execute;
124             return $retval; # because its undef it hasn't been used
125             }
126              
127             1;
128              
129             __END__