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