File Coverage

blib/lib/DBIx/PgLink/Adapter/SybaseASE.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::PgLink::Adapter::SybaseASE;
2              
3             # tested on Sybase ASE 12.5 with DBD::Sybase 0.95
4              
5 1     1   2007 use Carp;
  1         3  
  1         75  
6 1     1   442 use Moose;
  0            
  0            
7             use MooseX::Method;
8             use Data::Dumper;
9             use DBI qw/:sql_types/;
10              
11             extends 'DBIx::PgLink::Adapter::SQLServer';
12              
13             has '+require_parameter_type' => (default=>1);
14              
15             override 'quote_identifier' => sub {
16             my ($self, @id) = @_;
17              
18             for my $i (@id) {
19             next unless defined $i;
20             next if $i =~ /^\w+$/; # quote only when needed
21             $i =~ s/"/""/g;
22             $i = '"' . $i . '"';
23             }
24             my $quoted_id = join '.', grep { defined } @id;
25             return $quoted_id;
26             };
27              
28              
29             # for Reconnect role
30             sub is_disconnected {
31             my ($self, $exception) = @_;
32             return
33             $exception =~ /Net-Library operation terminated due to disconnect/i
34             ;
35             }
36              
37             # ---------------------------- data conversion
38              
39             # PostgreSQL boolean to bit
40             sub pg_bool_to_syb_bit {
41             $_[1] = defined $_[1] ? $_[1] eq 't' ? 1 : 0 : 0; # NULL is not allowed
42             }
43              
44             # Sybase (var)?binary to bytea
45             sub syb_binary_to_pg_bytea {
46             my ($self) = @_;
47             $_[1] =~ s/^0x//;
48             $_[1] = pack("H*", $_[1]);
49             $self->to_pg_bytea($_[1]);
50             }
51              
52              
53             # catalog functions very picky about input
54              
55             around 'table_info' => sub {
56             my ($next, $self, $catalog, $schema, $table, $type) = @_;
57              
58             # catalog must be current database name
59             if (!$catalog || $catalog eq '%') {
60             $catalog = $self->current_database();
61             }
62             # type entries must be quoted
63             if ($type !~ /'/) {
64             $type = join ',', map { "'$_'" } split /,/, $type;
65             }
66             $next->($self, $catalog, $schema, $table, $type);
67             # cannot fix column names here
68             };
69              
70              
71             around 'column_info' => sub {
72             my ($next, $self, $catalog, $schema, $table, $column) = @_;
73              
74             # catalog must be current database name
75             if (!$catalog || $catalog eq '%') {
76             $catalog = $self->current_database();
77             }
78             $next->($self, $catalog, $schema, $table, $column);
79             };
80              
81              
82             sub _uppercase_hashref_keys {
83             my $href = shift;
84             my @keys = keys %{$href};
85             for my $key (@keys) {
86             $href->{uc $key} = delete $href->{$key};
87             }
88             }
89              
90              
91             around 'expand_table_info' => sub {
92             my ($next, $self, $table) = @_;
93              
94             _uppercase_hashref_keys($table);
95              
96             # bug: DBD::Sybase v0.95 return non-standard field name
97             $table->{TABLE_CAT} ||= $table->{TABLE_QUALIFIER};
98             $table->{TABLE_SCHEM} ||= $table->{TABLE_OWNER};
99              
100             $next->($self, $table);
101             };
102              
103              
104             around 'expand_column_info', 'expand_primary_key_info' => sub {
105             my ($next, $self, $info) = @_;
106              
107             _uppercase_hashref_keys($info);
108              
109             $next->($self, $info);
110             };
111              
112              
113             # DBD::Sybase has problem with placeholders in prepared SP call
114             for my $func (qw/
115             prepare prepare_cached
116             /) {
117             around $func => sub {
118             my $next = shift;
119             my $self = shift;
120             my $statement = shift;
121             my $attr = shift;
122              
123             if ($statement =~ /^EXEC.*\?/i && $self->dbh->{Driver}->{Name} eq 'Sybase') {
124             return $self->new_statement(
125             class => 'DBIx::PgLink::Adapter::SybaseASE::PreparedProcedure',
126             statement => $statement,
127             parent => $self,
128             method => $func,
129             defined $attr ? %{$attr} : (),
130             );
131             } else {
132             return $next->($self, $statement, $attr);
133             }
134             };
135             }
136              
137             has 'quote_literal_types' => ( # initialize once for connection
138             is=>'ro', isa=>'HashRef', lazy=>1,
139             default=>sub {
140             return {
141             SQL_BINARY() => undef,
142             SQL_BLOB() => undef,
143             SQL_CHAR() => undef,
144             SQL_DATE() => undef,
145             SQL_VARBINARY() => undef,
146             SQL_VARCHAR() => undef,
147             } },
148             );
149              
150              
151              
152              
153             around 'routine_info_arrayref' => sub {
154             my ($next, $self, $catalog, $schema, $routine, $type) = @_;
155              
156             # TODO: parse $type and add Java function support (very low priority)
157             return $next->($self, $catalog, $schema, $routine, $type) unless $type =~ /PROCEDURE/;
158              
159             my @result = ();
160              
161             my $full_proc_name = $self->quote_identifier(
162             $catalog,
163             'dbo',
164             'sp_stored_procedures'
165             );
166             my $sth = $self->prepare("exec $full_proc_name ?, ?, ?");
167             $sth->execute( # name in reverse order
168             $routine,
169             $schema,
170             $catalog,
171             );
172              
173             while (my $sp = $sth->fetchrow_hashref) {
174             my $proc_name = $sp->{procedure_name};
175             $proc_name =~ s/;\d+$//; # obsolete procedure group number
176             my $i = {
177             SPECIFIC_CATALOG => $sp->{procedure_qualifier},
178             SPECIFIC_SCHEMA => $sp->{procedure_owner},
179             SPECIFIC_NAME => $proc_name,
180             ROUTINE_CATALOG => $sp->{procedure_qualifier},
181             ROUTINE_SCHEMA => $sp->{procedure_owner},
182             ROUTINE_NAME => $proc_name,
183             ROUTINE_TYPE => 'PROCEDURE',
184             DATA_TYPE => undef,
185             };
186             $self->expand_routine_info($i)
187             and push @result, $i;
188             }
189             $sth->finish;
190              
191             return \@result;
192              
193             };
194              
195              
196             around 'expand_routine_argument_info' => sub {
197             my ($next, $self, $arg) = @_;
198              
199             _uppercase_hashref_keys($arg);
200              
201             $next->($self, $arg);
202             };
203              
204              
205             sub dummy_procedure_call_arguments {
206             my ($self, $routine_info) = @_;
207             # 'bit' type does not allow NULL
208             return map {
209             $_->{DATA_TYPE} == SQL_BIT ? '0' : 'NULL'
210             } @{$self->routine_argument_info_arrayref($routine_info)};
211             }
212              
213              
214             1;
215              
216              
217             # emulate prepared statement for DBD::Sybase
218              
219             package DBIx::PgLink::Adapter::SybaseASE::PreparedProcedure;
220              
221             use Moose;
222             use DBIx::PgLink::Adapter;
223             use DBIx::PgLink::Logger;
224             use DBI qw/:sql_types/;
225              
226             extends 'DBIx::PgLink::Adapter::st';
227              
228             has '+sth' => ( required=>0 );
229              
230             has 'statement' => ( is=>'ro', isa=>'Str', required=>1 );
231              
232             has 'method' => ( is=>'ro', isa=>'Str', required=>1 );
233              
234             has 'param_values' => ( is=>'rw', isa=>'ArrayRef', default=>sub { [] } );
235              
236             around 'finish' => sub {
237             my $next = shift;
238             my $self = shift;
239              
240             $self->param_values( [] );
241             $self->sth->finish if $self->sth;
242             };
243              
244              
245             around 'bind_param' => sub {
246             my $next = shift;
247             my ($self, $p_num, $bind_value, $attr) = @_;
248              
249             my $type = ref $attr eq 'HASH' ? $attr->{TYPE} : $attr;
250              
251             if (exists $self->parent->quote_literal_types->{$type}) {
252             $bind_value = $self->parent->quote($bind_value);
253             }
254             $self->param_values->[$p_num-1] = $bind_value;
255             trace_msg('INFO', "Bind $p_num " . (defined $bind_value ? $bind_value : 'NULL'))
256             if trace_level >= 4;
257             return 1;
258             };
259              
260              
261             around 'execute' => sub {
262             my $next = shift;
263             my $self = shift;
264              
265             for my $i (0..$#_) {
266             $self->bind_param($i+1, $_[$i], SQL_VARCHAR);
267             }
268              
269             # naive replacement of ?-placeholders by literal value
270             my $query = $self->statement;
271             for my $p (@{$self->param_values}) {
272             $query =~ s/\?/$p/;
273             }
274              
275             $self->sth->finish if $self->sth;
276             my $sth = $self->method eq 'prepare_cached'
277             ? $self->parent->dbh->prepare_cached($query)
278             : $self->parent->dbh->prepare($query);
279             $self->{sth} = $sth;
280             return $sth->execute;
281             };
282              
283              
284             1;