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