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=7aff37c99aefe83bf0a5b5331c69cb67f4032846;hb=55f68788a1e0975e4cc36e0623bf4b70c514b46e;hp=f263fb3ebdb7f99484fa0513bd269cc4b08e4f4f;hpb=14c82fd49d8561d20f7d9ce1dda6572975dcf7ac;p=dbsrgits%2FDBIx-Class-Historic.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 f263fb3..7aff37c 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. + +B on FreeTDS, changes made in one statement (e.g. an insert) may not +be visible from a following statement (e.g. a select.) + +=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; - if ($identity_insert) { - my $table = $source->from; - $self->_get_dbh->do("SET IDENTITY_INSERT $table ON"); - } + $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info') + if ref($dbi_inf->[0]) eq 'CODE'; - $self->next::method(@_); + # reenter connection information with the attribute re-set + $dbi_inf->[3] = {} if @$dbi_inf <= 3; + $dbi_inf->[3]{odbc_cursortype} = 2; - if ($identity_insert) { - my $table = $source->from; - $self->_get_dbh->do("SET IDENTITY_INSERT $table OFF"); + $self->_dbi_connect_info($dbi_inf); + + $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) = @_; - my ($sql, $bind) = $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); + } + } - if ($op eq 'insert') { - $sql .= ';SELECT SCOPE_IDENTITY()'; + $self->next::method (@_); +} - my $col_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]); - if (List::Util::first { $_->{is_auto_increment} } (values %$col_info) ) { +=head2 connect_call_use_server_cursors - 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"; - } +Use as: + + 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; =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