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