File Coverage

blib/lib/Class/DBI/Sybase.pm
Criterion Covered Total %
statement 33 138 23.9
branch 0 46 0.0
condition 0 18 0.0
subroutine 11 24 45.8
pod 0 2 0.0
total 44 228 19.3


line stmt bran cond sub pod time code
1             package Class::DBI::Sybase;
2              
3             =head1 NAME
4              
5             Class::DBI::Sybase - Extensions to Class::DBI for Sybase
6              
7             =head1 SYNOPSIS
8              
9             package Music::DBI;
10             use base 'Class::DBI::Sybase';
11             Music::DBI->set_db('Main', "dbi:Sybase:server=$server", $username, $password);
12              
13             package Artist;
14             use base 'Music::DBI';
15             __PACKAGE__->set_up_table('Artist');
16            
17             # ... see the Class::DBI documentation for details on Class::DBI usage
18              
19             =head1 DESCRIPTION
20              
21             This is an extension to Class::DBI that currently implements:
22              
23             * Automatic column name discovery.
24             * Works with IDENTITY columns to auto-generate primary keys.
25             * Works with TEXT columns for create() and update()
26             * Allow for CaseSensitive columns (for JavaDeveloperDesignedDBs)
27             * Allow for tables with multiple primary key columns
28            
29             Instead of setting Class::DBI as your base class, use this.
30              
31             =head1 BUGS
32              
33             DBD::Sybase currently has a bug where a statement handle can be marked as
34             active, even though it's not. We override sth_to_objects to call finish() on the handle.
35              
36             =head1 AUTHORS
37              
38             * Dan Sully Edaniel@cpan.orgE - Original Author
39              
40             * Michael Wojcikewicz Etheothermike@gmail.comE - Current Maintainer
41              
42             * Paul Sandulescu Earchpollux@gmail.comE - Patches
43              
44             * Thai Nguyen Euseevil@gmail.comE - Patches
45              
46             =head1 SEE ALSO
47              
48             L, L
49              
50             =cut
51              
52              
53 1     1   56562 use strict;
  1         2  
  1         43  
54 1     1   13 use base 'Class::DBI';
  1         3  
  1         2087  
55              
56 1     1   134257 use vars qw($VERSION);
  1         10  
  1         2310  
57             $VERSION = '0.5';
58              
59             # new Column and ColumnGrouper classes for CaseSensitivity
60              
61 0     0     sub _die { require Carp; Carp::croak(@_); }
  0            
62              
63             # This is necessary to get the last ID back
64             __PACKAGE__->set_sql( MakeNewObj => <<'');
65             SET NOCOUNT ON
66             INSERT INTO __TABLE__ (%s)
67             VALUES (%s)
68             SELECT @@IDENTITY
69              
70              
71             # This is necessary for CaseSensitivity
72             __PACKAGE__->__grouper( Class::DBI::Sybase::ColumnGrouper->new() );
73              
74             sub set_up_table
75             {
76 0     0 0   my ( $class, $table ) = @_;
77 0           my $dbh = $class->db_Main();
78              
79 0           $class->table($table);
80              
81             # find the primary key and column names.
82 0           my $sth = $dbh->prepare("sp_columns $table");
83 0           $sth->execute();
84              
85 0           my $col = $sth->fetchall_arrayref;
86 0           $sth->finish();
87              
88 0 0         _die( 'The "' . $class->table() . '" table has no primary key' ) unless $col->[0][3];
89              
90 0           $class->columns( All => map { $_->[3] } @$col );
  0            
91 0           $class->columns( Primary => $col->[0][3] );
92              
93             # find any text columns that will get quoted upon INSERT
94 0 0         $class->columns( TEXT => map { $_->[5] eq 'text' ? $_->[3] : () } @$col );
  0            
95              
96             # now find the IDENTITY column
97 0           $sth = $dbh->prepare("sp_help $table");
98 0           $sth->execute();
99              
100             # the first two resultsets contain no info about finding the identity column
101 0           $sth->fetchall_arrayref() for 1 .. 2;
102 0           $col = $sth->fetchall_arrayref();
103              
104 0           my ($identity) = grep( $_->[9] == 1, @$col ); # the 10th column contains a boolean denoting whether it's an IDENTITY
105 0 0         $class->columns( IDENTITY => $identity->[0] ) if $identity; # store the IDENTITY column
106             }
107              
108             # Fixes a DBD::Sybase problem where the handle is still active.
109             # allows for CaseSensitive columns
110             sub sth_to_objects
111             {
112 0     0 0   my ( $class, $sth, $args ) = @_;
113              
114 0 0         $class->_croak("sth_to_objects needs a statement handle") unless $sth;
115              
116 0 0         unless ( UNIVERSAL::isa( $sth => "DBI::st" ) )
117             {
118 0           my $meth = "sql_$sth";
119 0           $sth = $class->$meth();
120             }
121 0 0         $sth->finish() if $sth->{Active};
122              
123             # reimplement the rest of Class::DBI::sth_to_objects, without NAME_lc
124 0           my ( %data, @rows );
125              
126 0           eval {
127 0 0         $sth->execute(@$args) unless $sth->{Active};
128 0           $sth->bind_columns( \( @data{ @{ $sth->{NAME} } } ) );
  0            
129 0           push @rows, {%data} while $sth->fetch;
130             };
131              
132 0 0         return $class->_croak( "$class can't $sth->{Statement}: $@", err => $@ ) if $@;
133 0           return $class->_ids_to_objects( \@rows );
134             }
135              
136             sub _column_placeholder
137             {
138 0     0     my $self = shift;
139 0           my $column = shift;
140 0           my $data = shift;
141 0           my @text_columns = $self->columns('TEXT');
142              
143             # if its a text column, we need to $dbh -> quote() it, rather than using a placeholder, limitation of Sybase TDS libraries
144 0 0 0       if ( $data && grep { $_ eq $column } @text_columns )
  0            
145             {
146 0           return $self->db_Main->quote($data);
147             }
148             else
149             {
150 0           return $self->SUPER::_column_placeholder($column);
151             }
152             }
153              
154             sub _insert_row
155             {
156 0     0     my $self = shift;
157 0           my $data = shift;
158 0           my @primary_columns = $self->primary_columns();
159 0           my @identity_columns = $self->columns('IDENTITY');
160 0           my @text_columns = $self->columns('TEXT');
161              
162 0           eval {
163 0           my @columns;
164             my @values;
165              
166             # Omit the IDENTITY column to let it be Auto Generated
167 0           for my $column ( keys %$data )
168             {
169 0 0 0       next if defined $identity_columns[0] && $column eq $identity_columns[0];
170              
171 0           push @columns, $column;
172              
173             # Omit the text column since it needs to be quoted
174 0 0         push @values, $data->{$column} unless grep { $_ eq $column } @text_columns;
  0            
175             }
176 0           my $sth = $self->sql_MakeNewObj(
177             join( ', ', @columns ),
178             join( ', ', map $self->_column_placeholder( $_, $data->{$_} ), @columns )
179             , # this uses the new placeholder methods that quotes
180             );
181 0           $self->_bind_param( $sth, \@columns );
182 0           $sth->execute(@values);
183              
184 0           my $id = $sth->fetchrow_arrayref()->[0];
185              
186 0 0 0       $data->{ $identity_columns[0] } = $id
187             if @identity_columns == 1
188             && !defined $data->{ $identity_columns[0] };
189 0 0         $sth->finish if $sth->{Active};
190             };
191              
192 0 0         if ($@)
193             {
194 0           my $class = ref $self;
195 0           return $self->_croak(
196             "Can't insert new $class: $@",
197             err => $@,
198             method => 'create'
199             );
200             }
201              
202 0           return 1;
203             }
204              
205             sub _update_vals
206             {
207 0     0     my $self = shift;
208 0           my @text_columns = $self->columns('TEXT');
209 0           my @identity_columns = $self->columns('IDENTITY');
210              
211 0           my @changed = $self->is_changed();
212 0           my @columns;
213              
214 0           foreach my $changed (@changed)
215             {
216              
217             # omit TEXT and IDENTITY columns from the update clause since they are quoted
218 0 0         next if grep { $_ eq $changed } @identity_columns;
  0            
219 0 0         next if grep { $_ eq $changed } @text_columns;
  0            
220              
221 0           push @columns, $changed;
222             }
223              
224 0           return $self->_attrs(@columns);
225             }
226              
227             sub _update_line
228             {
229 0     0     my $self = shift;
230 0           my @changed = $self->is_changed;
231 0           my @identity_columns = $self->columns('IDENTITY');
232 0           my @columns;
233              
234 0           foreach my $changed (@changed)
235             {
236              
237             # omit IDENTITY columns from the update clause since they are cannot be
238             # changed without first setting IDENTITY_INSERT to ON
239 0 0         push @columns, $changed unless grep { $_ eq $changed } @identity_columns;
  0            
240             }
241              
242             # use our custom _column_placeholder that quotes TEXT columns
243 0           return join( ', ', map "$_ = " . $self->_column_placeholder( $_, $self->$_() ), @columns );
244             }
245              
246             sub _make_method
247             {
248 0     0     my ( $class, $name, $method ) = @_;
249              
250 0 0         return if defined &{"$class\::$name"};
  0            
251              
252 0 0 0       $class->_carp("Column '$name' in $class clashes with built-in method")
      0        
253             if Class::DBI->can($name)
254             and not( $name eq "id" and join( " ", $class->primary_columns ) eq "id" );
255              
256 1     1   9 no strict 'refs';
  1         3  
  1         220  
257 0           *{"$class\::$name"} = $method;
  0            
258              
259 0           $class->_make_method( $name => $method );
260             }
261              
262 0     0     sub _column_class {'Class::DBI::Sybase::Column'} # for CaseSensitivity
263              
264             sub _set_columns
265             {
266 0     0     my ( $class, $group, @columns ) = @_;
267 0 0         my @cols = map ref $_ ? $_ : $class->_column_class->new($_), @columns;
268              
269             # Careful to take copy
270 0           $class->__grouper( Class::DBI::Sybase::ColumnGrouper->clone( $class->__grouper )->add_group( $group => @cols ) );
271 0           $class->_mk_column_accessors(@cols);
272              
273 0           return @columns;
274             }
275              
276             1;
277              
278             package Class::DBI::Sybase::Column;
279              
280 1     1   6 use strict;
  1         2  
  1         38  
281 1     1   7 use base 'Class::DBI::Column';
  1         9  
  1         124  
282 1     1   5 use Carp;
  1         2  
  1         2820  
283              
284             # dont lower case
285 0     0     sub name_lc { shift->name }
286              
287             1;
288              
289             package Class::DBI::Sybase::ColumnGrouper;
290              
291 1     1   33 use strict;
  1         2  
  1         49  
292 1     1   6 use Carp;
  1         2  
  1         106  
293 1     1   8 use Storable 'dclone';
  1         2  
  1         65  
294              
295 1     1   8 use base qw( Class::DBI::ColumnGrouper );
  1         1  
  1         420  
296              
297             sub add_column
298             {
299 0     0     my ($self, $col) = @_;
300              
301             # dont lower case
302 0 0         croak "Need a Column, got $col" unless $col->isa("Class::DBI::Column");
303 0   0       $self->{_allcol}->{ $col->name } ||= $col;
304             }
305              
306             sub find_column
307             {
308 0     0     my ($self, $name) = @_;
309              
310             # dont lower case
311 0 0         return $name if ref $name;
312 0 0         return unless $self->{_allcol}->{ $name };
313             }
314              
315             # TODO: LIMIT ?
316