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