1package DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server;
2use strict;
3use warnings;
4
5use base qw/DBIx::Class::Storage::DBI::MSSQL/;
6use mro 'c3';
7
8use List::Util();
9use Scalar::Util ();
10
11__PACKAGE__->mk_group_accessors(simple => qw/
12  _using_dynamic_cursors
13/);
14
15=head1 NAME
16
17DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server - Support specific
18to Microsoft SQL Server over ODBC
19
20=head1 DESCRIPTION
21
22This class implements support specific to Microsoft SQL Server over ODBC.  It is
23loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it detects a
24MSSQL back-end.
25
26Most of the functionality is provided from the superclass
27L<DBIx::Class::Storage::DBI::MSSQL>.
28
29=head1 MULTIPLE ACTIVE STATEMENTS
30
31The following options are alternative ways to enable concurrent executing
32statement support. Each has its own advantages and drawbacks.
33
34=head2 connect_call_use_dynamic_cursors
35
36Use as:
37
38  on_connect_call => 'use_dynamic_cursors'
39
40in your L<DBIx::Class::Storage::DBI/connect_info> as one way to enable multiple
41concurrent statements.
42
43Will add C<< odbc_cursortype => 2 >> to your DBI connection attributes. See
44L<DBD::ODBC/odbc_cursortype> for more information.
45
46Alternatively, you can add it yourself and dynamic cursor support will be
47automatically enabled.
48
49If you're using FreeTDS, C<tds_version> must be set to at least C<8.0>.
50
51This will not work with CODE ref connect_info's.
52
53B<WARNING:> this will break C<SCOPE_IDENTITY()>, and C<SELECT @@IDENTITY> will
54be used instead, which on SQL Server 2005 and later will return erroneous
55results on tables which have an on insert trigger that inserts into another
56table with an C<IDENTITY> column.
57
58=cut
59
60sub connect_call_use_dynamic_cursors {
61  my $self = shift;
62
63  if (ref($self->_dbi_connect_info->[0]) eq 'CODE') {
64    $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info');
65  }
66
67  my $dbi_attrs = $self->_dbi_connect_info->[-1];
68
69  unless (ref($dbi_attrs) && Scalar::Util::reftype($dbi_attrs) eq 'HASH') {
70    $dbi_attrs = {};
71    push @{ $self->_dbi_connect_info }, $dbi_attrs;
72  }
73
74  if (not exists $dbi_attrs->{odbc_cursortype}) {
75    # turn on support for multiple concurrent statements, unless overridden
76    $dbi_attrs->{odbc_cursortype} = 2;
77    $self->disconnect; # resetting dbi attrs, so have to reconnect
78    $self->ensure_connected;
79    $self->_set_dynamic_cursors;
80  }
81}
82
83sub _set_dynamic_cursors {
84  my $self = shift;
85  my $dbh  = $self->_get_dbh;
86
87  eval {
88    local $dbh->{RaiseError} = 1;
89    local $dbh->{PrintError} = 0;
90    $dbh->do('SELECT @@IDENTITY');
91  };
92  if ($@) {
93    $self->throw_exception (<<'EOF');
94
95Your drivers do not seem to support dynamic cursors (odbc_cursortype => 2),
96if you're using FreeTDS, make sure to set tds_version to 8.0 or greater.
97EOF
98  }
99
100  $self->_using_dynamic_cursors(1);
101  $self->_identity_method('@@identity');
102}
103
104sub _init {
105  my $self = shift;
106
107  no warnings qw/uninitialized/;
108
109  if (
110    ref($self->_dbi_connect_info->[0]) ne 'CODE'
111      &&
112    ref ($self->_dbi_connect_info->[-1]) eq 'HASH'
113      &&
114    $self->_dbi_connect_info->[-1]{odbc_cursortype} == 2
115  ) {
116    $self->_set_dynamic_cursors;
117    return;
118  }
119
120  $self->_using_dynamic_cursors(0);
121}
122
123=head2 connect_call_use_server_cursors
124
125Use as:
126
127  on_connect_call => 'use_server_cursors'
128
129May allow multiple active select statements. See
130L<DBD::ODBC/odbc_SQL_ROWSET_SIZE> for more information.
131
132Takes an optional parameter for the value to set the attribute to, default is
133C<2>.
134
135B<WARNING>: this does not work on all versions of SQL Server, and may lock up
136your database!
137
138=cut
139
140sub connect_call_use_server_cursors {
141  my $self            = shift;
142  my $sql_rowset_size = shift || 2;
143
144  $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size;
145}
146
147=head2 connect_call_use_MARS
148
149Use as:
150
151  on_connect_call => 'use_MARS'
152
153Use to enable a feature of SQL Server 2005 and later, "Multiple Active Result
154Sets". See L<DBD::ODBC::FAQ/Does DBD::ODBC support Multiple Active Statements?>
155for more information.
156
157B<WARNING>: This has implications for the way transactions are handled.
158
159=cut
160
161sub connect_call_use_MARS {
162  my $self = shift;
163
164  my $dsn = $self->_dbi_connect_info->[0];
165
166  if (ref($dsn) eq 'CODE') {
167    $self->throw_exception('cannot change the DBI DSN on a CODE ref connect_info');
168  }
169
170  if ($dsn !~ /MARS_Connection=/) {
171    $self->_dbi_connect_info->[0] = "$dsn;MARS_Connection=Yes";
172    my $was_connected = defined $self->_dbh;
173    $self->disconnect;
174    $self->ensure_connected if $was_connected;
175  }
176}
177
178sub _get_mssql_version {
179  my $self = shift;
180
181  my ($version) = $self->_get_dbh->get_info(18) =~ /^(\d+)/;
182
183  return $version;
184}
185
1861;
187
188=head1 AUTHOR
189
190See L<DBIx::Class/CONTRIBUTORS>.
191
192=head1 LICENSE
193
194You may distribute this code under the same terms as Perl itself.
195
196=cut
197# vim: sw=2 sts=2
198