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