Cleanup exception handling
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / ODBC / Microsoft_SQL_Server.pm
1 package DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server;
2 use strict;
3 use warnings;
4
5 use base qw/DBIx::Class::Storage::DBI::MSSQL/;
6 use mro 'c3';
7
8 use List::Util();
9 use Scalar::Util ();
10
11 __PACKAGE__->mk_group_accessors(simple => qw/
12   _using_dynamic_cursors
13 /);
14
15 =head1 NAME
16
17 DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server - Support specific
18 to Microsoft SQL Server over ODBC
19
20 =head1 DESCRIPTION
21
22 This class implements support specific to Microsoft SQL Server over ODBC.  It is
23 loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it detects a
24 MSSQL back-end.
25
26 Most of the functionality is provided from the superclass
27 L<DBIx::Class::Storage::DBI::MSSQL>.
28
29 =head1 MULTIPLE ACTIVE STATEMENTS
30
31 The following options are alternative ways to enable concurrent executing
32 statement support. Each has its own advantages and drawbacks.
33
34 =head2 connect_call_use_dynamic_cursors
35
36 Use as:
37
38   on_connect_call => 'use_dynamic_cursors'
39
40 in your L<DBIx::Class::Storage::DBI/connect_info> as one way to enable multiple
41 concurrent statements.
42
43 Will add C<< odbc_cursortype => 2 >> to your DBI connection attributes. See
44 L<DBD::ODBC/odbc_cursortype> for more information.
45
46 Alternatively, you can add it yourself and dynamic cursor support will be
47 automatically enabled.
48
49 If you're using FreeTDS, C<tds_version> must be set to at least C<8.0>.
50
51 This will not work with CODE ref connect_info's.
52
53 B<WARNING:> this will break C<SCOPE_IDENTITY()>, and C<SELECT @@IDENTITY> will
54 be used instead, which on SQL Server 2005 and later will return erroneous
55 results on tables which have an on insert trigger that inserts into another
56 table with an C<IDENTITY> column.
57
58 =cut
59
60 sub 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
83 sub _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
95 Your drivers do not seem to support dynamic cursors (odbc_cursortype => 2),
96 if you're using FreeTDS, make sure to set tds_version to 8.0 or greater.
97 EOF
98   }
99
100   $self->_using_dynamic_cursors(1);
101   $self->_identity_method('@@identity');
102 }
103
104 sub _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
125 Use as:
126
127   on_connect_call => 'use_server_cursors'
128
129 May allow multiple active select statements. See
130 L<DBD::ODBC/odbc_SQL_ROWSET_SIZE> for more information.
131
132 Takes an optional parameter for the value to set the attribute to, default is
133 C<2>.
134
135 B<WARNING>: this does not work on all versions of SQL Server, and may lock up
136 your database!
137
138 =cut
139
140 sub 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
149 Use as:
150
151   on_connect_call => 'use_MARS'
152
153 Use to enable a feature of SQL Server 2005 and later, "Multiple Active Result
154 Sets". See L<DBD::ODBC::FAQ/Does DBD::ODBC support Multiple Active Statements?>
155 for more information.
156
157 B<WARNING>: This has implications for the way transactions are handled.
158
159 =cut
160
161 sub 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
178 1;
179
180 =head1 AUTHOR
181
182 See L<DBIx::Class/CONTRIBUTORS>.
183
184 =head1 LICENSE
185
186 You may distribute this code under the same terms as Perl itself.
187
188 =cut
189 # vim: sw=2 sts=2