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=0a58c76e45052249b576e20d06f591269b4c8df1;hpb=1da8d083227d3db4dee8f6392d7d06a66fd7155c;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 0a58c76..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,13 +2,18 @@ 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 Carp::Clan qw/^DBIx::Class/; -use List::Util(); +use Scalar::Util 'reftype'; +use Try::Tiny; +use DBIx::Class::Carp; +use namespace::clean; __PACKAGE__->mk_group_accessors(simple => qw/ - _scope_identity _using_dynamic_cursors + _using_dynamic_cursors /); =head1 NAME @@ -18,101 +23,78 @@ to Microsoft SQL Server over ODBC =head1 DESCRIPTION -This class implements support specific to Microsoft SQL Server over ODBC, -including auto-increment primary keys and SQL::Abstract::Limit dialect. It -is loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it -detects a MSSQL back-end. +This class implements support specific to Microsoft SQL Server over ODBC. It is +loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it detects a +MSSQL back-end. -=head1 IMPLEMENTATION NOTES +Most of the functionality is provided from the superclass +L. -Microsoft SQL Server supports three methods of retrieving the C -value for inserted row: C, C<@@IDENTITY>, and C. -C is used here because it is the safest. However, it must -be called is the same execute statement, not just the same connection. +=head1 USAGE NOTES -So, this implementation appends a C is used instead. + sudo aptitude install tdsodbc libdbd-odbc-perl unixodbc -=head1 MULTIPLE ACTIVE STATEMENTS +In case it is not already there put the following in C: -The following options are alternative ways to enable concurrent executing -statement support. Each has its own advantages and drawbacks. + [FreeTDS] + Description = FreeTDS + Driver = /usr/lib/odbc/libtdsodbc.so + Setup = /usr/lib/odbc/libtdsS.so + UsageCount = 1 -=head2 connect_call_use_dynamic_cursors +Set your C<$dsn> in L as follows: -Use as: + dbi:ODBC:server=;port=1433;driver=FreeTDS;tds_version=8.0 - on_connect_call => 'use_dynamic_cursors' +If you use the EasySoft driver (L): -in your L as one way to enable multiple -concurrent statements. + dbi:ODBC:server=;port=1433;driver=Easysoft ODBC-SQL Server -Will add C<< odbc_cursortype => 2 >> to your DBI connection attributes. See -L for more information. +=head2 Basic Windows Setup -This will not work with CODE ref connect_info's and will do nothing if you set -C yourself. +Use the following C<$dsn> for the Microsoft ODBC driver: -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; - my ($source, $cols, $data) = @_; - my $identity_insert = 0; + if (($self->_dbic_connect_attributes->{odbc_cursortype} || 0) < 2) { - COLUMNS: - foreach my $col (@{$cols}) { - if ($source->column_info($col)->{is_auto_increment}) { - $identity_insert = 1; - last COLUMNS; - } + my $dbi_inf = $self->_dbi_connect_info; + + $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); + + $self->disconnect; # resetting dbi attrs, so have to reconnect + $self->ensure_connected; } +} + +sub _run_connection_actions { + my $self = shift; - if ($identity_insert) { - my $table = $source->from; - $self->_get_dbh->do("SET IDENTITY_INSERT $table ON"); + $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 ( + $use_dyncursors + xor + !!$self->_using_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); + } } - $self->next::method(@_); + $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 + } - if ($identity_insert) { - my $table = $source->from; - $self->_get_dbh->do("SET IDENTITY_INSERT $table OFF"); + # FreeTDS is too broken wrt execute_for_fetch batching + # just disable it outright until things quiet down + $self->_disable_odbc_array_ops; } } -sub _prep_for_execute { - my $self = shift; - my ($op, $extra_bind, $ident, $args) = @_; +=head2 connect_call_use_server_cursors - my ($sql, $bind) = $self->next::method (@_); +Use as: - if ($op eq 'insert') { - $sql .= ';SELECT SCOPE_IDENTITY()'; + on_connect_call => 'use_server_cursors' - my $col_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]); - if (List::Util::first { $_->{is_auto_increment} } (values %$col_info) ) { +May allow multiple active select statements. See +L for more information. - my $table = $ident->from; - my $identity_insert_on = "SET IDENTITY_INSERT $table ON"; - my $identity_insert_off = "SET IDENTITY_INSERT $table OFF"; - $sql = "$identity_insert_on; $sql; $identity_insert_off"; - } - } +Takes an optional parameter for the value to set the attribute to, default is +C<2>. - return ($sql, $bind); -} +B: this does not work on all versions of SQL Server, and may lock up +your database! -sub _execute { - my $self = shift; - my ($op) = @_; +At the time of writing, this option only works on Microsoft's Windows drivers, +later versions of the ODBC driver and the Native Client driver. - my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_); - if ($op eq 'insert') { - my ($identity) = $sth->fetchrow_array; - $sth->finish; +=cut - if ((not defined $identity) && $self->_using_dynamic_cursors) { - ($identity) = $self->_dbh->selectrow_array('select @@identity'); - } +sub connect_call_use_server_cursors { + my $self = shift; + my $sql_rowset_size = shift || 2; - $self->_scope_identity($identity); - } + if ($^O !~ /win32|cygwin/i) { + $self->throw_exception('Server cursors only work on Windows platforms at ' + .'the time of writing.'); + } - return wantarray ? ($rv, $sth, @bind) : $rv; + $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size; } -sub last_insert_id { shift->_scope_identity() } - 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