refactor code needing version
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / MSSQL.pm
CommitLineData
75d07914 1package DBIx::Class::Storage::DBI::MSSQL;
3885cff6 2
75d07914 3use strict;
4use warnings;
3885cff6 5
fabbd5cc 6use base qw/
7 DBIx::Class::Storage::DBI::UniqueIdentifier
8 DBIx::Class::Storage::DBI::IdentityInsert
9/;
2ad62d97 10use mro 'c3';
fabbd5cc 11
ed7ab0f4 12use Try::Tiny;
6298a324 13use List::Util 'first';
fd323bf1 14use namespace::clean;
3885cff6 15
7b1b2582 16__PACKAGE__->mk_group_accessors(simple => qw/
25d3127d 17 _identity _identity_method _no_scope_identity_query
7b1b2582 18/);
19
d5dedbd6 20__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::MSSQL');
ac93965c 21
2b8cc2f2 22__PACKAGE__->sql_quote_char([qw/[ ]/]);
23
6f7a118e 24__PACKAGE__->datetime_parser_type (
25 'DBIx::Class::Storage::DBI::MSSQL::DateTime::Format'
26);
27
40d8d018 28__PACKAGE__->new_guid('NEWID()');
29
fe5a0374 30sub _sql_server_2005_or_higher {
31 if (exists $_[0]->_server_info->{normalized_dbms_version}) {
32 if ($_[0]->_server_info->{normalized_dbms_version} >= 9) {
33 return 1
34 } else {
35 return 0
36 }
37 }
38 return undef;
39}
40
5a77aa8b 41sub _prep_for_execute {
42 my $self = shift;
0e773352 43 my ($op, $ident, $args) = @_;
5a77aa8b 44
45# cast MONEY values properly
46 if ($op eq 'insert' || $op eq 'update') {
47 my $fields = $args->[0];
5a77aa8b 48
52416317 49 my $colinfo = $ident->columns_info([keys %$fields]);
50
5a77aa8b 51 for my $col (keys %$fields) {
1537084d 52 # $ident is a result source object with INSERT/UPDATE ops
52416317 53 if (
54 $colinfo->{$col}{data_type}
55 &&
56 $colinfo->{$col}{data_type} =~ /^money\z/i
57 ) {
5a77aa8b 58 my $val = $fields->{$col};
59 $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
60 }
61 }
62 }
63
64 my ($sql, $bind) = $self->next::method (@_);
65
fabbd5cc 66 # SELECT SCOPE_IDENTITY only works within a statement scope. We
4a0eed52 67 # must try to always use this particular idiom first, as it is the
fabbd5cc 68 # only one that guarantees retrieving the correct id under high
69 # concurrency. When this fails we will fall back to whatever secondary
70 # retrieval method is specified in _identity_method, but at this
71 # point we don't have many guarantees we will get what we expected.
72 # http://msdn.microsoft.com/en-us/library/ms190315.aspx
73 # http://davidhayden.com/blog/dave/archive/2006/01/17/2736.aspx
25d3127d 74 if ($self->_perform_autoinc_retrieval and not $self->_no_scope_identity_query) {
384b8bce 75 $sql .= "\nSELECT SCOPE_IDENTITY()";
5a77aa8b 76 }
77
78 return ($sql, $bind);
79}
80
81sub _execute {
82 my $self = shift;
5a77aa8b 83
fabbd5cc 84 # always list ctx - we need the $sth
0e773352 85 my ($rv, $sth, @bind) = $self->next::method(@_);
1537084d 86
fabbd5cc 87 if ($self->_perform_autoinc_retrieval) {
5a77aa8b 88
25d3127d 89 # attempt to bring back the result of SELECT SCOPE_IDENTITY() we tacked
1537084d 90 # on in _prep_for_execute above
25d3127d 91 my $identity;
92
93 # we didn't even try on ftds
94 unless ($self->_no_scope_identity_query) {
95 ($identity) = try { $sth->fetchrow_array };
96 $sth->finish;
97 }
ed8de058 98
1537084d 99 # SCOPE_IDENTITY failed, but we can do something else
100 if ( (! $identity) && $self->_identity_method) {
101 ($identity) = $self->_dbh->selectrow_array(
102 'select ' . $self->_identity_method
103 );
104 }
7b1b2582 105
1537084d 106 $self->_identity($identity);
7b1b2582 107 }
108
1537084d 109 return wantarray ? ($rv, $sth, @bind) : $rv;
7b1b2582 110}
5a77aa8b 111
7b1b2582 112sub last_insert_id { shift->_identity }
5a77aa8b 113
f0bd60fc 114#
e74c68ce 115# MSSQL is retarded wrt ordered subselects. One needs to add a TOP
6a247f33 116# to *all* subqueries, but one also *can't* use TOP 100 PERCENT
e74c68ce 117# http://sqladvice.com/forums/permalink/18496/22931/ShowThread.aspx#22931
f0bd60fc 118#
119sub _select_args_to_query {
b928a9d5 120 #my ($self, $ident, $select, $cond, $attrs) = @_;
f0bd60fc 121 my $self = shift;
b928a9d5 122 my $attrs = $_[3];
f0bd60fc 123
b928a9d5 124 my $sql_bind = $self->next::method (@_);
f0bd60fc 125
b8d88d9b 126 # see if this is an ordered subquery
aca481d8 127 if (
b928a9d5 128 $$sql_bind->[0] !~ /^ \s* \( \s* SELECT \s+ TOP \s+ \d+ \s+ /xi
129 and
bac358c9 130 scalar $self->_extract_order_criteria ($attrs->{order_by})
aca481d8 131 ) {
6de07ea3 132 $self->throw_exception(
e705f529 133 'An ordered subselect encountered - this is not safe! Please see "Ordered Subselects" in DBIx::Class::Storage::DBI::MSSQL'
134 ) unless $attrs->{unsafe_subselect_ok};
b928a9d5 135
136 $$sql_bind->[0] =~ s/^ \s* \( \s* SELECT (?=\s) / '(SELECT TOP ' . $self->sql_maker->__max_int /exi;
f0bd60fc 137 }
138
b928a9d5 139 $sql_bind;
f0bd60fc 140}
141
142
4c0f4206 143# savepoint syntax is the same as in Sybase ASE
144
90d7422f 145sub _exec_svp_begin {
4c0f4206 146 my ($self, $name) = @_;
147
90d7422f 148 $self->_dbh->do("SAVE TRANSACTION $name");
4c0f4206 149}
150
151# A new SAVE TRANSACTION with the same name releases the previous one.
90d7422f 152sub _exec_svp_release { 1 }
4c0f4206 153
90d7422f 154sub _exec_svp_rollback {
4c0f4206 155 my ($self, $name) = @_;
156
90d7422f 157 $self->_dbh->do("ROLLBACK TRANSACTION $name");
4c0f4206 158}
159
eb0323df 160sub sqlt_type { 'SQLServer' }
161
6a247f33 162sub sql_limit_dialect {
50772633 163 my $self = shift;
eb0323df 164
fe5a0374 165 my $supports_rno = $self->_sql_server_2005_or_higher;
ff153e24 166
fe5a0374 167 unless (defined $supports_rno) {
6a247f33 168 # User is connecting via DBD::Sybase and has no permission to run
169 # stored procedures like xp_msver, or version detection failed for some
170 # other reason.
171 # So, we use a query to check if RNO is implemented.
172 try {
173 $self->_get_dbh->selectrow_array('SELECT row_number() OVER (ORDER BY rand())');
174 $supports_rno = 1;
175 };
50772633 176 }
e76e7b5c 177
6a247f33 178 return $supports_rno ? 'RowNumberOver' : 'Top';
ed8de058 179}
3885cff6 180
ecdf1ac8 181sub _ping {
182 my $self = shift;
183
184 my $dbh = $self->_dbh or return 0;
185
186 local $dbh->{RaiseError} = 1;
187 local $dbh->{PrintError} = 0;
188
52b420dd 189 return try {
ecdf1ac8 190 $dbh->do('select 1');
52b420dd 191 1;
ed7ab0f4 192 } catch {
52b420dd 193 0;
ecdf1ac8 194 };
ecdf1ac8 195}
196
fb95dc4d 197package # hide from PAUSE
198 DBIx::Class::Storage::DBI::MSSQL::DateTime::Format;
199
fd323bf1 200my $datetime_format = '%Y-%m-%d %H:%M:%S.%3N'; # %F %T
fb95dc4d 201my $smalldatetime_format = '%Y-%m-%d %H:%M:%S';
202
203my ($datetime_parser, $smalldatetime_parser);
204
205sub parse_datetime {
206 shift;
207 require DateTime::Format::Strptime;
208 $datetime_parser ||= DateTime::Format::Strptime->new(
209 pattern => $datetime_format,
210 on_error => 'croak',
211 );
212 return $datetime_parser->parse_datetime(shift);
213}
214
215sub format_datetime {
216 shift;
217 require DateTime::Format::Strptime;
218 $datetime_parser ||= DateTime::Format::Strptime->new(
219 pattern => $datetime_format,
220 on_error => 'croak',
221 );
222 return $datetime_parser->format_datetime(shift);
223}
224
225sub parse_smalldatetime {
226 shift;
227 require DateTime::Format::Strptime;
228 $smalldatetime_parser ||= DateTime::Format::Strptime->new(
229 pattern => $smalldatetime_format,
230 on_error => 'croak',
231 );
232 return $smalldatetime_parser->parse_datetime(shift);
233}
234
235sub format_smalldatetime {
236 shift;
237 require DateTime::Format::Strptime;
238 $smalldatetime_parser ||= DateTime::Format::Strptime->new(
239 pattern => $smalldatetime_format,
240 on_error => 'croak',
241 );
242 return $smalldatetime_parser->format_datetime(shift);
243}
244
75d07914 2451;
3885cff6 246
75d07914 247=head1 NAME
3885cff6 248
5a77aa8b 249DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
250in DBIx::Class
3885cff6 251
75d07914 252=head1 SYNOPSIS
3885cff6 253
5a77aa8b 254This is the base class for Microsoft SQL Server support, used by
255L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
256L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
eb0323df 257
5a77aa8b 258=head1 IMPLEMENTATION NOTES
eb0323df 259
fd05d10a 260=head2 IDENTITY information
261
5a77aa8b 262Microsoft SQL Server supports three methods of retrieving the IDENTITY
263value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
264SCOPE_IDENTITY is used here because it is the safest. However, it must
265be called is the same execute statement, not just the same connection.
eb0323df 266
5a77aa8b 267So, this implementation appends a SELECT SCOPE_IDENTITY() statement
268onto each INSERT to accommodate that requirement.
eb0323df 269
7b1b2582 270C<SELECT @@IDENTITY> can also be used by issuing:
271
272 $self->_identity_method('@@identity');
273
08cdc412 274it will only be used if SCOPE_IDENTITY() fails.
275
276This is more dangerous, as inserting into a table with an on insert trigger that
277inserts into another table with an identity will give erroneous results on
278recent versions of SQL Server.
7b1b2582 279
c84189e1 280=head2 identity insert
fd05d10a 281
282Be aware that we have tried to make things as simple as possible for our users.
c84189e1 283For MSSQL that means that when a user tries to create a row, while supplying an
284explicit value for an autoincrementing column, we will try to issue the
285appropriate database call to make this possible, namely C<SET IDENTITY_INSERT
286$table_name ON>. Unfortunately this operation in MSSQL requires the
287C<db_ddladmin> privilege, which is normally not included in the standard
288write-permissions.
fd05d10a 289
d74f2da9 290=head2 Ordered Subselects
6de07ea3 291
d74f2da9 292If you attempted the following query (among many others) in Microsoft SQL
293Server
6de07ea3 294
6de07ea3 295 $rs->search ({}, {
6de07ea3 296 prefetch => 'relation',
297 rows => 2,
298 offset => 3,
299 });
300
d74f2da9 301You may be surprised to receive an exception. The reason for this is a quirk
302in the MSSQL engine itself, and sadly doesn't have a sensible workaround due
303to the way DBIC is built. DBIC can do truly wonderful things with the aid of
304subselects, and does so automatically when necessary. The list of situations
305when a subselect is necessary is long and still changes often, so it can not
306be exhaustively enumerated here. The general rule of thumb is a joined
307L<has_many|DBIx::Class::Relationship/has_many> relationship with limit/group
308applied to the left part of the join.
309
310In its "pursuit of standards" Microsft SQL Server goes to great lengths to
311forbid the use of ordered subselects. This breaks a very useful group of
312searches like "Give me things number 4 to 6 (ordered by name), and prefetch
313all their relations, no matter how many". While there is a hack which fools
314the syntax checker, the optimizer may B<still elect to break the subselect>.
315Testing has determined that while such breakage does occur (the test suite
316contains an explicit test which demonstrates the problem), it is relative
317rare. The benefits of ordered subselects are on the other hand too great to be
318outright disabled for MSSQL.
6de07ea3 319
320Thus compromise between usability and perfection is the MSSQL-specific
69a8b315 321L<resultset attribute|DBIx::Class::ResultSet/ATTRIBUTES> C<unsafe_subselect_ok>.
6de07ea3 322It is deliberately not possible to set this on the Storage level, as the user
48580715 323should inspect (and preferably regression-test) the return of every such
d74f2da9 324ResultSet individually. The example above would work if written like:
325
326 $rs->search ({}, {
69a8b315 327 unsafe_subselect_ok => 1,
d74f2da9 328 prefetch => 'relation',
329 rows => 2,
330 offset => 3,
331 });
6de07ea3 332
333If it is possible to rewrite the search() in a way that will avoid the need
334for this flag - you are urged to do so. If DBIC internals insist that an
d74f2da9 335ordered subselect is necessary for an operation, and you believe there is a
48580715 336different/better way to get the same result - please file a bugreport.
6de07ea3 337
5a77aa8b 338=head1 AUTHOR
3885cff6 339
548d1627 340See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
3885cff6 341
75d07914 342=head1 LICENSE
3885cff6 343
75d07914 344You may distribute this code under the same terms as Perl itself.
3885cff6 345
75d07914 346=cut