From: Rafael Kitover Date: Fri, 24 Jul 2009 06:46:16 +0000 (+0000) Subject: merge in some more MSSQL code, including odbc dynamic cursor support X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=7b1b2582a2f787fcacd238f1635fbce4cb9e0985;p=dbsrgits%2FDBIx-Class-Historic.git merge in some more MSSQL code, including odbc dynamic cursor support --- diff --git a/lib/DBIx/Class/Storage/DBI/MSSQL.pm b/lib/DBIx/Class/Storage/DBI/MSSQL.pm index fe51f1b..5bf3c10 100644 --- a/lib/DBIx/Class/Storage/DBI/MSSQL.pm +++ b/lib/DBIx/Class/Storage/DBI/MSSQL.pm @@ -8,6 +8,10 @@ use mro 'c3'; use List::Util(); +__PACKAGE__->mk_group_accessors(simple => qw/ + _identity _identity_method +/); + __PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MSSQL'); sub insert_bulk { @@ -78,15 +82,26 @@ sub _execute { my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_); if ($op eq 'insert') { - $self->{_scope_identity} = $sth->fetchrow_array; - $sth->finish; + $self->_identity($self->_fetch_identity($sth)); } return wantarray ? ($rv, $sth, @bind) : $rv; } +sub _fetch_identity { + my ($self, $sth) = @_; + my ($identity) = $sth->fetchrow_array; + $sth->finish; + + if ((not defined $identity) && $self->_identity_method && + $self->_identity_method eq '@@identity') { + ($identity) = $self->_dbh->selectrow_array('select @@identity'); + } + + return $identity; +} -sub last_insert_id { shift->{_scope_identity} } +sub last_insert_id { shift->_identity } sub build_datetime_parser { my $self = shift; @@ -131,6 +146,13 @@ be called is the same execute statement, not just the same connection. So, this implementation appends a SELECT SCOPE_IDENTITY() statement onto each INSERT to accommodate that requirement. +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; + + if (ref($self->_dbi_connect_info->[0]) eq 'CODE') { + croak 'cannot set DBI attributes on a CODE ref connect_info'; + } + + my $dbi_attrs = $self->_dbi_connect_info->[-1]; + + unless (ref($dbi_attrs) && Scalar::Util::reftype($dbi_attrs) eq 'HASH') { + $dbi_attrs = {}; + push @{ $self->_dbi_connect_info }, $dbi_attrs; + } + + if (not exists $dbi_attrs->{odbc_cursortype}) { + # turn on support for multiple concurrent statements, unless overridden + $dbi_attrs->{odbc_cursortype} = 2; + my $connected = defined $self->_dbh; + $self->disconnect; + $self->ensure_connected if $connected; + $self->_set_dynamic_cursors; + } +} + +sub _set_dynamic_cursors { + my $self = shift; + $self->_using_dynamic_cursors(1); + $self->_identity_method('@@identity'); +} + +sub _rebless { + no warnings 'uninitialized'; + my $self = shift; + + if (ref($self->_dbi_connect_info->[0]) ne 'CODE' && + eval { $self->_dbi_connect_info->[-1]{odbc_cursortype} } == 2) { + $self->_set_dynamic_cursors; + return; + } + + $self->_using_dynamic_cursors(0); +} + +=head2 connect_call_use_server_cursors + +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! + +=cut + +sub connect_call_use_server_cursors { + my $self = shift; + my $sql_rowset_size = shift || 2; + + $self->_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size; +} + +=head2 connect_call_use_mars + +Use as: + + on_connect_call => 'use_mars' + +Use to enable a feature of SQL Server 2005 and later, "Multiple Active Result +Sets". See L +for more information. + +B: This has implications for the way transactions are handled. + +=cut + +sub connect_call_use_mars { + my $self = shift; + + my $dsn = $self->_dbi_connect_info->[0]; + + if (ref($dsn) eq 'CODE') { + croak 'cannot change the DBI DSN on a CODE ref connect_info'; + } + + if ($dsn !~ /MARS_Connection=/) { + $self->_dbi_connect_info->[0] = "$dsn;MARS_Connection=Yes"; + my $connected = defined $self->_dbh; + $self->disconnect; + $self->ensure_connected if $connected; + } +} + +1; + =head1 AUTHOR See L. diff --git a/t/746mssql.t b/t/746mssql.t index fa8f137..e5be28a 100644 --- a/t/746mssql.t +++ b/t/746mssql.t @@ -12,7 +12,7 @@ my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ODBC_${_}" } qw/DSN USER PA plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN}, _USER and _PASS to run this test' unless ($dsn && $user); -plan tests => 33; +plan tests => 34; my $schema = DBICTest::Schema->connect($dsn, $user, $pass); @@ -48,12 +48,22 @@ SQL my %seen_id; -# fresh $schema so we start unconnected -$schema = DBICTest::Schema->connect($dsn, $user, $pass); +my @opts = ( + { on_connect_call => 'use_dynamic_cursors' }, + {}, +); +my $new; -# test primary key handling -my $new = $schema->resultset('Artist')->create({ name => 'foo' }); -ok($new->artistid > 0, "Auto-PK worked"); +# test Auto-PK with different options +for my $opts (@opts) { + $schema = DBICTest::Schema->clone; + $schema->connection($dsn, $user, $pass, $opts); + + $schema->resultset('Artist')->search({ name => 'foo' })->delete; + + $new = $schema->resultset('Artist')->create({ name => 'foo' }); + ok($new->artistid > 0, "Auto-PK worked"); +} $seen_id{$new->artistid}++;