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=bedf113686a0c8e1fa15e5a70fe04cc74989c4f8;hb=e46df41a7023dc31ac5eba4bc81c050d7964d3be;hp=9757eca3e56c203806b379078bfe2df9fdd1fb7a;hpb=820eebf7142f5a0bead4a67b522cea0a4523c82b;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 9757eca..bedf113 100644 --- a/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm +++ b/lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm @@ -4,11 +4,13 @@ use warnings; use base qw/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/ - _identity _using_dynamic_cursors + _using_dynamic_cursors /); =head1 NAME @@ -18,101 +20,74 @@ 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. + +=cut + +sub connect_call_use_dynamic_cursors { + my $self = shift; + + my $conn_info = $self->_dbi_connect_info; + + if (ref($conn_info->[0]) eq 'CODE') { + $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info'); + } + + if ( + ref($conn_info->[-1]) ne 'HASH' + or + ($conn_info->[-1]{odbc_cursortype}||0) < 2 + ) { + # reenter connection information with the attribute re-set + $self->connect_info( + @{$conn_info}[0,1,2], + { %{$self->_dbix_connect_attributes}, odbc_cursortype => 2 }, + ); + $self->disconnect; # resetting dbi attrs, so have to reconnect + $self->ensure_connected; } } -sub _prep_for_execute { +sub _run_connection_actions { my $self = shift; - my ($op, $extra_bind, $ident, $args) = @_; - -# cast MONEY values properly - if ($op eq 'insert' || $op eq 'update') { - my $fields = $args->[0]; - my $col_info = $self->_resolve_column_info($ident, [keys %$fields]); - - for my $col (keys %$fields) { - if ($col_info->{$col}{data_type} =~ /^money\z/i) { - my $val = $fields->{$col}; - $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]]; - } + + # 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); } } - my ($sql, $bind) = $self->next::method (@_); + $self->next::method (@_); +} - if ($op eq 'insert') { - $sql .= ';SELECT SCOPE_IDENTITY()'; +=head2 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) ) { +Use as: - 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"; - } + on_connect_call => 'use_server_cursors' + +May allow multiple active select statements. See +L for more information. + +Takes an optional parameter for the value to set the attribute to, default is +C<2>. + +B: this does not work on all versions of SQL Server, and may lock up +your database! + +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. + +=cut + +sub connect_call_use_server_cursors { + my $self = shift; + my $sql_rowset_size = shift || 2; + + if ($^O !~ /win32|cygwin/i) { + $self->throw_exception('Server cursors only work on Windows platforms at ' + .'the time of writing.'); } - return ($sql, $bind); + $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size; } -sub _execute { - my $self = shift; - my ($op) = @_; +=head2 using_freetds - my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_); - if ($op eq 'insert') { - my ($identity) = $sth->fetchrow_array; - $sth->finish; +Tries to determine, to the best of our ability, whether or not you are using the +FreeTDS driver with L. - if ((not defined $identity) && $self->_using_dynamic_cursors) { - ($identity) = $self->_dbh->selectrow_array('select @@identity'); - } +=cut - $self->_identity($identity); - } +sub using_freetds { + my $self = shift; - return wantarray ? ($rv, $sth, @bind) : $rv; -} + my $dsn = $self->_dbi_connect_info->[0]; + + $dsn = '' if ref $dsn eq 'CODE'; -sub last_insert_id { shift->_identity() } + return 1 if $dsn =~ /driver=FreeTDS/i + || ($self->_dbh_get_info(6)||'') =~ /tdsodbc/i; + + return 0; +} 1; @@ -239,5 +303,4 @@ See L. You may distribute this code under the same terms as Perl itself. =cut - # vim: sw=2 sts=2