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