add a link to doc on MAS for MSSQL+ODBC
[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/
6   DBIx::Class::Storage::DBI::ODBC
7   DBIx::Class::Storage::DBI::MSSQL
8 /;
9 use mro 'c3';
10 use Scalar::Util 'reftype';
11 use Try::Tiny;
12 use DBIx::Class::Carp;
13 use namespace::clean;
14
15 __PACKAGE__->mk_group_accessors(simple => qw/
16   _using_dynamic_cursors
17 /);
18
19 =head1 NAME
20
21 DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server - Support specific
22 to Microsoft SQL Server over ODBC
23
24 =head1 DESCRIPTION
25
26 This class implements support specific to Microsoft SQL Server over ODBC.  It is
27 loaded automatically by by DBIx::Class::Storage::DBI::ODBC when it detects a
28 MSSQL back-end.
29
30 Most of the functionality is provided from the superclass
31 L<DBIx::Class::Storage::DBI::MSSQL>.
32
33 =head1 USAGE NOTES
34
35 =head2 Basic Linux Setup (Debian)
36
37   sudo aptitude install tdsodbc libdbd-odbc-perl unixodbc
38
39 In case it is not already there put the following in C</etc/odbcinst.ini>:
40
41   [FreeTDS]
42   Description = FreeTDS
43   Driver      = /usr/lib/odbc/libtdsodbc.so
44   Setup       = /usr/lib/odbc/libtdsS.so
45   UsageCount  = 1
46
47 Set your C<$dsn> in L<connect_info|DBIx::Class::Storage::DBI/connect_info> as follows:
48
49   dbi:ODBC:server=<my.host.name>;port=1433;driver=FreeTDS;tds_version=8.0
50
51 If you use the EasySoft driver (L<http://www.easysoft.com>):
52
53   dbi:ODBC:server=<my.host.name>;port=1433;driver=Easysoft ODBC-SQL Server
54
55 =head2 Basic Windows Setup
56
57 Use the following C<$dsn> for the Microsoft ODBC driver:
58
59   dbi:ODBC:driver={SQL Server};server=SERVER\SQL_SERVER_INSTANCE_NAME
60
61 And for the Native Client:
62
63   dbi:ODBC:driver={SQL Server Native Client 10.0};server=SERVER\SQL_SERVER_INSTANCE_NAME
64
65 Go into Control Panel -> System and Security -> Administrative Tools -> Data
66 Sources (ODBC) to check driver names and to set up data sources.
67
68 Use System DSNs, not User DSNs if you want to use DSNs.
69
70 If you set up a DSN, use the following C<$dsn> for
71 L<connect_info|DBIx::Class::Storage::DBI/connect_info>:
72
73   dbi:ODBC:dsn=MY_DSN
74
75 =head1 MULTIPLE ACTIVE STATEMENTS
76
77 The following options are alternative ways to enable concurrent executing
78 statement support. Each has its own advantages and drawbacks and works on
79 different platforms. Read each section carefully.
80
81 For more details about using MAS in MSSQL over DBD::ODBC see this excellent
82 document provided by EasySoft:
83 L<http://www.easysoft.com/developer/languages/perl/multiple-active-statements.html>.
84
85 In order of preference, they are:
86
87 =over 8
88
89 =item * L<mars|/connect_call_use_mars>
90
91 =item * L<dynamic_cursors|/connect_call_use_dynamic_cursors>
92
93 =item * L<server_cursors|/connect_call_use_server_cursors>
94
95 =back
96
97 =head1 METHODS
98
99 =head2 connect_call_use_mars
100
101 Use as:
102
103   on_connect_call => 'use_mars'
104
105 in your connection info, or alternatively specify it directly:
106
107   Your::Schema->connect (
108     $original_dsn . '; MARS_Connection=Yes',
109     $user,
110     $pass,
111     \%attrs,
112   )
113
114 Use to enable a feature of SQL Server 2005 and later, "Multiple Active Result
115 Sets". See L<DBD::ODBC::FAQ/Does DBD::ODBC support Multiple Active Statements?>
116 for more information.
117
118 This does not work on FreeTDS drivers at the time of this writing, and only
119 works with the Native Client, later versions of the Windows MS ODBC driver, and
120 the Easysoft driver.
121
122 =cut
123
124 sub connect_call_use_mars {
125   my $self = shift;
126
127   my $dsn = $self->_dbi_connect_info->[0];
128
129   if (ref($dsn) eq 'CODE') {
130     $self->throw_exception('cannot change the DBI DSN on a CODE ref connect_info');
131   }
132
133   if ($dsn !~ /MARS_Connection=/) {
134     if ($self->_using_freetds) {
135       $self->throw_exception('FreeTDS does not support MARS at the time of '
136                             .'writing.');
137     }
138
139     if (exists $self->_server_info->{normalized_dbms_version} &&
140                $self->_server_info->{normalized_dbms_version} < 9) {
141       $self->throw_exception('SQL Server 2005 or later required to use MARS.');
142     }
143
144     if (my ($data_source) = $dsn =~ /^dbi:ODBC:([\w-]+)\z/i) { # prefix with DSN
145       warn "Bare DSN in ODBC connect string, rewriting as 'dsn=$data_source'"
146           ." for MARS\n";
147       $dsn = "dbi:ODBC:dsn=$data_source";
148     }
149
150     $self->_dbi_connect_info->[0] = "$dsn;MARS_Connection=Yes";
151     $self->disconnect;
152     $self->ensure_connected;
153   }
154 }
155
156 sub connect_call_use_MARS {
157   carp "'connect_call_use_MARS' has been deprecated, use "
158       ."'connect_call_use_mars' instead.";
159   shift->connect_call_use_mars(@_)
160 }
161
162 =head2 connect_call_use_dynamic_cursors
163
164 Use as:
165
166   on_connect_call => 'use_dynamic_cursors'
167
168 Which will add C<< odbc_cursortype => 2 >> to your DBI connection
169 attributes, or alternatively specify the necessary flag directly:
170
171   Your::Schema->connect (@dsn, { ... odbc_cursortype => 2 })
172
173 See L<DBD::ODBC/odbc_cursortype> for more information.
174
175 If you're using FreeTDS, C<tds_version> must be set to at least C<8.0>.
176
177 This will not work with CODE ref connect_info's.
178
179 B<WARNING:> on FreeTDS (and maybe some other drivers) this will break
180 C<SCOPE_IDENTITY()>, and C<SELECT @@IDENTITY> will be used instead, which on SQL
181 Server 2005 and later will return erroneous results on tables which have an on
182 insert trigger that inserts into another table with an C<IDENTITY> column.
183
184 B<WARNING:> on FreeTDS, changes made in one statement (e.g. an insert) may not
185 be visible from a following statement (e.g. a select.)
186
187 B<WARNING:> FreeTDS versions > 0.82 seem to have completely broken the ODBC
188 protocol. DBIC will not allow dynamic cursor support with such versions to
189 protect your data. Please hassle the authors of FreeTDS to act on the bugs that
190 make their driver not overly usable with DBD::ODBC.
191
192 =cut
193
194 sub connect_call_use_dynamic_cursors {
195   my $self = shift;
196
197   if (($self->_dbic_connect_attributes->{odbc_cursortype} || 0) < 2) {
198
199     my $dbi_inf = $self->_dbi_connect_info;
200
201     $self->throw_exception ('Cannot set DBI attributes on a CODE ref connect_info')
202       if ref($dbi_inf->[0]) eq 'CODE';
203
204     # reenter connection information with the attribute re-set
205     $dbi_inf->[3] = {} if @$dbi_inf <= 3;
206     $dbi_inf->[3]{odbc_cursortype} = 2;
207
208     $self->_dbi_connect_info($dbi_inf);
209
210     $self->disconnect; # resetting dbi attrs, so have to reconnect
211     $self->ensure_connected;
212   }
213 }
214
215 sub _run_connection_actions {
216   my $self = shift;
217
218   $self->next::method (@_);
219
220   # keep the dynamic_cursors_support and driver-state in sync
221   # on every reconnect
222   my $use_dyncursors = ($self->_dbic_connect_attributes->{odbc_cursortype} || 0) > 1;
223   if (
224     $use_dyncursors
225       xor
226     !!$self->_using_dynamic_cursors
227   ) {
228     if ($use_dyncursors) {
229       try {
230         my $dbh = $self->_dbh;
231         local $dbh->{RaiseError} = 1;
232         local $dbh->{PrintError} = 0;
233         $dbh->do('SELECT @@IDENTITY');
234       } catch {
235         $self->throw_exception (
236           'Your drivers do not seem to support dynamic cursors (odbc_cursortype => 2).'
237          . (
238           $self->_using_freetds
239             ? ' If you are using FreeTDS, make sure to set tds_version to 8.0 or greater.'
240             : ''
241           )
242         );
243       };
244
245       $self->_using_dynamic_cursors(1);
246       $self->_identity_method('@@identity');
247     }
248     else {
249       $self->_using_dynamic_cursors(0);
250       $self->_identity_method(undef);
251     }
252   }
253
254   $self->_no_scope_identity_query($self->_using_dynamic_cursors
255     ? $self->_using_freetds
256     : undef
257   );
258
259   # freetds is too damn broken, some fixups
260   if ($self->_using_freetds) {
261
262     # no dynamic cursors starting from 0.83
263     if ($self->_using_dynamic_cursors) {
264       my $fv = $self->_using_freetds_version || 999;  # assume large if can't be determined
265       $self->throw_exception(
266         'Dynamic cursors (odbc_cursortype => 2) are not supported with FreeTDS > 0.82 '
267       . "(you have $fv). Please hassle FreeTDS authors to fix the outstanding bugs in "
268       . 'their driver.'
269       ) if $fv > 0.82
270     }
271
272     # FreeTDS is too broken wrt execute_for_fetch batching
273     # just disable it outright until things quiet down
274     $self->_disable_odbc_array_ops;
275   }
276 }
277
278 =head2 connect_call_use_server_cursors
279
280 Use as:
281
282   on_connect_call => 'use_server_cursors'
283
284 May allow multiple active select statements. See
285 L<DBD::ODBC/odbc_SQL_ROWSET_SIZE> for more information.
286
287 Takes an optional parameter for the value to set the attribute to, default is
288 C<2>.
289
290 B<WARNING>: this does not work on all versions of SQL Server, and may lock up
291 your database!
292
293 At the time of writing, this option only works on Microsoft's Windows drivers,
294 later versions of the ODBC driver and the Native Client driver.
295
296 =cut
297
298 sub connect_call_use_server_cursors {
299   my $self            = shift;
300   my $sql_rowset_size = shift || 2;
301
302   if ($^O !~ /win32|cygwin/i) {
303     $self->throw_exception('Server cursors only work on Windows platforms at '
304                           .'the time of writing.');
305   }
306
307   $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size;
308 }
309
310 1;
311
312 =head1 AUTHOR
313
314 See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
315
316 =head1 LICENSE
317
318 You may distribute this code under the same terms as Perl itself.
319
320 =cut
321 # vim:sw=2 sts=2 et