X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FODBC%2FMicrosoft_SQL_Server.pm;h=3aa9b9bd7b6f1cb2f189c2bed70e8ae85cc108c5;hb=2b6d7e87bdf3f09e79cc19d209e52e78000f0578;hp=eef33fe53f1920c63a0a50bf41b3d9cc11885661;hpb=a469c0453b89a1cd45e66e35f3a25a200d9f065f;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm b/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm index eef33fe..3aa9b9b 100644 --- a/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm +++ b/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm @@ -2,11 +2,14 @@ package DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server; use strict; use warnings; -use base qw/DBIx::Class::Storage::DBI::MSSQL/; +use base qw/ + DBIx::Class::Storage::DBI::ODBC + DBIx::Class::Storage::DBI::MSSQL +/; use mro 'c3'; use Scalar::Util 'reftype'; use Try::Tiny; -use Carp::Clan qw/^DBIx::Class/; +use DBIx::Class::Carp; use namespace::clean; __PACKAGE__->mk_group_accessors(simple => qw/ @@ -75,15 +78,19 @@ The following options are alternative ways to enable concurrent executing statement support. Each has its own advantages and drawbacks and works on different platforms. Read each section carefully. +For more details about using MAS in MSSQL over DBD::ODBC see this excellent +document provided by EasySoft: +L. + In order of preference, they are: =over 8 -=item * L +=item * L -=item * L +=item * L -=item * L +=item * L =back @@ -95,6 +102,15 @@ Use as: on_connect_call => 'use_mars' +in your connection info, or alternatively specify it directly: + + Your::Schema->connect ( + $original_dsn . '; MARS_Connection=Yes', + $user, + $pass, + \%attrs, + ) + Use to enable a feature of SQL Server 2005 and later, "Multiple Active Result Sets". See L for more information. @@ -115,7 +131,7 @@ sub connect_call_use_mars { } if ($dsn !~ /MARS_Connection=/) { - if ($self->using_freetds) { + if ($self->_using_freetds) { $self->throw_exception('FreeTDS does not support MARS at the time of ' .'writing.'); } @@ -149,83 +165,113 @@ Use as: on_connect_call => 'use_dynamic_cursors' -in your L as one way to enable multiple -concurrent statements. +Which will add C<< odbc_cursortype => 2 >> to your DBI connection +attributes, or alternatively specify the necessary flag directly: -Will add C<< odbc_cursortype => 2 >> to your DBI connection attributes. See -L for more information. + Your::Schema->connect (@dsn, { ... odbc_cursortype => 2 }) -Alternatively, you can add it yourself and dynamic cursor support will be -automatically enabled. +See L for more information. If you're using FreeTDS, C must be set to at least C<8.0>. This will not work with CODE ref connect_info's. -B this will break C, and C will be used instead, which on SQL +Server 2005 and later will return erroneous results on tables which have an on +insert trigger that inserts into another table with an C column. + +B on FreeTDS, changes made in one statement (e.g. an insert) may not +be visible from a following statement (e.g. a select.) + +B FreeTDS versions > 0.82 seem to have completely broken the ODBC +protocol. DBIC will not allow dynamic cursor support with such versions to +protect your data. Please hassle the authors of FreeTDS to act on the bugs that +make their driver not overly usable with DBD::ODBC. =cut sub connect_call_use_dynamic_cursors { my $self = shift; - if (ref($self->_dbi_connect_info->[0]) eq 'CODE') { - $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info'); - } + if (($self->_dbic_connect_attributes->{odbc_cursortype} || 0) < 2) { - my $dbi_attrs = $self->_dbi_connect_info->[-1]; + my $dbi_inf = $self->_dbi_connect_info; - unless (ref $dbi_attrs eq 'HASH') { - $dbi_attrs = {}; - push @{ $self->_dbi_connect_info }, $dbi_attrs; - } + $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info') + if ref($dbi_inf->[0]) eq 'CODE'; + + # reenter connection information with the attribute re-set + $dbi_inf->[3] = {} if @$dbi_inf <= 3; + $dbi_inf->[3]{odbc_cursortype} = 2; + + $self->_dbi_connect_info($dbi_inf); - if (not exists $dbi_attrs->{odbc_cursortype}) { - # turn on support for multiple concurrent statements, unless overridden - $dbi_attrs->{odbc_cursortype} = 2; $self->disconnect; # resetting dbi attrs, so have to reconnect $self->ensure_connected; - $self->_set_dynamic_cursors; } } -sub _set_dynamic_cursors { +sub _run_connection_actions { my $self = shift; - my $dbh = $self->_get_dbh; - - try { - local $dbh->{RaiseError} = 1; - local $dbh->{PrintError} = 0; - $dbh->do('SELECT @@IDENTITY'); - } catch { - $self->throw_exception (<<'EOF'); - -Your drivers do not seem to support dynamic cursors (odbc_cursortype => 2), -if you're using FreeTDS, make sure to set tds_version to 8.0 or greater. -EOF - }; - - $self->_using_dynamic_cursors(1); - $self->_identity_method('@@identity'); -} -sub _init { - my $self = shift; + $self->next::method (@_); + # keep the dynamic_cursors_support and driver-state in sync + # on every reconnect + my $use_dyncursors = ($self->_dbic_connect_attributes->{odbc_cursortype} || 0) > 1; if ( - ref($self->_dbi_connect_info->[0]) ne 'CODE' - && - ref ($self->_dbi_connect_info->[-1]) eq 'HASH' - && - ($self->_dbi_connect_info->[-1]{odbc_cursortype} || 0) > 1 + $use_dyncursors + xor + !!$self->_using_dynamic_cursors ) { - $self->_set_dynamic_cursors; + if ($use_dyncursors) { + try { + my $dbh = $self->_dbh; + local $dbh->{RaiseError} = 1; + local $dbh->{PrintError} = 0; + $dbh->do('SELECT @@IDENTITY'); + } catch { + $self->throw_exception ( + 'Your drivers do not seem to support dynamic cursors (odbc_cursortype => 2).' + . ( + $self->_using_freetds + ? ' If you are using FreeTDS, make sure to set tds_version to 8.0 or greater.' + : '' + ) + ); + }; + + $self->_using_dynamic_cursors(1); + $self->_identity_method('@@identity'); + } + else { + $self->_using_dynamic_cursors(0); + $self->_identity_method(undef); + } } - else { - $self->_using_dynamic_cursors(0); + + $self->_no_scope_identity_query($self->_using_dynamic_cursors + ? $self->_using_freetds + : undef + ); + + # freetds is too damn broken, some fixups + if ($self->_using_freetds) { + + # no dynamic cursors starting from 0.83 + if ($self->_using_dynamic_cursors) { + my $fv = $self->_using_freetds_version || 999; # assume large if can't be determined + $self->throw_exception( + 'Dynamic cursors (odbc_cursortype => 2) are not supported with FreeTDS > 0.82 ' + . "(you have $fv). Please hassle FreeTDS authors to fix the outstanding bugs in " + . 'their driver.' + ) if $fv > 0.82 + } + + # FreeTDS is too broken wrt execute_for_fetch batching + # just disable it outright until things quiet down + $self->_disable_odbc_array_ops; } } @@ -261,37 +307,15 @@ sub connect_call_use_server_cursors { $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size; } -=head2 using_freetds - -Tries to determine, to the best of our ability, whether or not you are using the -FreeTDS driver with L. - -=cut - -sub using_freetds { - my $self = shift; - - my $dsn = $self->_dbi_connect_info->[0]; - - $dsn = '' if ref $dsn eq 'CODE'; - - my $dbh = $self->_get_dbh; - - return 1 if $dsn =~ /driver=FreeTDS/i - || (try { $dbh->get_info(6) }||'') =~ /tdsodbc/i; - - return 0; -} - 1; =head1 AUTHOR -See L. +See L and L. =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut -# vim: sw=2 sts=2 +# vim:sw=2 sts=2 et