6dd308554d26896c1035790718b8131a1511bf00
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / MSSQL.pm
1 package DBIx::Class::Storage::DBI::MSSQL;
2
3 use strict;
4 use warnings;
5
6 use base qw/DBIx::Class::Storage::DBI::AmbiguousGlob DBIx::Class::Storage::DBI/;
7 use mro 'c3';
8
9 use List::Util();
10
11 __PACKAGE__->mk_group_accessors(simple => qw/
12   _identity _identity_method
13 /);
14
15 __PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MSSQL');
16
17 sub _set_identity_insert {
18   my ($self, $table) = @_;
19
20   my $sql = sprintf (
21     'SET IDENTITY_INSERT %s ON',
22     $self->sql_maker->_quote ($table),
23   );
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   }
33 }
34
35 sub _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
47 sub insert_bulk {
48   my $self = shift;
49   my ($source, $cols, $data) = @_;
50
51   my $is_identity_insert = (List::Util::first
52       { $source->column_info ($_)->{is_auto_increment} }
53       (@{$cols})
54   )
55      ? 1
56      : 0;
57
58   if ($is_identity_insert) {
59      $self->_set_identity_insert ($source->name);
60   }
61
62   $self->next::method(@_);
63
64   if ($is_identity_insert) {
65      $self->_unset_identity_insert ($source->name);
66   }
67 }
68
69 # support MSSQL GUID column types
70
71 sub insert {
72   my $self = shift;
73   my ($source, $to_insert) = @_;
74
75   my $supplied_col_info = $self->_resolve_column_info($source, [keys %$to_insert] );
76
77   my %guid_cols;
78   my @pk_cols = $source->primary_columns;
79   my %pk_cols;
80   @pk_cols{@pk_cols} = ();
81
82   my @pk_guids = grep {
83     $source->column_info($_)->{data_type}
84     &&
85     $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
86   } @pk_cols;
87
88   my @auto_guids = grep {
89     $source->column_info($_)->{data_type}
90     &&
91     $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
92     &&
93     $source->column_info($_)->{auto_nextval}
94   } grep { not exists $pk_cols{$_} } $source->columns;
95
96   my @get_guids_for =
97     grep { not exists $to_insert->{$_} } (@pk_guids, @auto_guids);
98
99   my $updated_cols = {};
100
101   for my $guid_col (@get_guids_for) {
102     my ($new_guid) = $self->_get_dbh->selectrow_array('SELECT NEWID()');
103     $updated_cols->{$guid_col} = $to_insert->{$guid_col} = $new_guid;
104   }
105
106   my $is_identity_insert = (List::Util::first { $_->{is_auto_increment} } (values %$supplied_col_info) )
107      ? 1
108      : 0;
109
110   if ($is_identity_insert) {
111      $self->_set_identity_insert ($source->name);
112   }
113
114   $updated_cols = { %$updated_cols, %{ $self->next::method(@_) } };
115
116   if ($is_identity_insert) {
117      $self->_unset_identity_insert ($source->name);
118   }
119
120
121   return $updated_cols;
122 }
123
124 sub _prep_for_execute {
125   my $self = shift;
126   my ($op, $extra_bind, $ident, $args) = @_;
127
128 # cast MONEY values properly
129   if ($op eq 'insert' || $op eq 'update') {
130     my $fields = $args->[0];
131
132     for my $col (keys %$fields) {
133       # $ident is a result source object with INSERT/UPDATE ops
134       if ($ident->column_info ($col)->{data_type}
135          &&
136          $ident->column_info ($col)->{data_type} =~ /^money\z/i) {
137         my $val = $fields->{$col};
138         $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
139       }
140     }
141   }
142
143   my ($sql, $bind) = $self->next::method (@_);
144
145   if ($op eq 'insert') {
146     $sql .= ';SELECT SCOPE_IDENTITY()';
147
148   }
149
150   return ($sql, $bind);
151 }
152
153 sub _execute {
154   my $self = shift;
155   my ($op) = @_;
156
157   my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
158
159   if ($op eq 'insert') {
160
161     # this should bring back the result of SELECT SCOPE_IDENTITY() we tacked
162     # on in _prep_for_execute above
163     my ($identity) = eval { $sth->fetchrow_array };
164
165     # SCOPE_IDENTITY failed, but we can do something else
166     if ( (! $identity) && $self->_identity_method) {
167       ($identity) = $self->_dbh->selectrow_array(
168         'select ' . $self->_identity_method
169       );
170     }
171
172     $self->_identity($identity);
173     $sth->finish;
174   }
175
176   return wantarray ? ($rv, $sth, @bind) : $rv;
177 }
178
179 sub last_insert_id { shift->_identity }
180
181 #
182 # MSSQL is retarded wrt ordered subselects. One needs to add a TOP 100%
183 # to *all* subqueries, do it here.
184 #
185 sub _select_args_to_query {
186   my $self = shift;
187
188   # _select_args does some shady action at a distance
189   # see DBI.pm for more info
190   my $sql_maker = $self->sql_maker;
191   my ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset);
192   {
193     local $sql_maker->{_dbic_rs_attrs};
194     ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset) = $self->_select_args(@_);
195   }
196
197   if (
198     ($rows || $offset)
199       ||
200     not scalar $sql_maker->_order_by_chunks ($order->{order_by})
201   ) {
202     # either limited RS or no ordering, just short circuit
203     return $self->next::method (@_);
204   }
205
206   my ($sql, $prep_bind, @rest) = $self->next::method (@_);
207   $sql =~ s/^ \s* SELECT \s/SELECT TOP 100 PERCENT /xi;
208
209   return wantarray
210     ? ($sql, $prep_bind, @rest)
211     : \[ "($sql)", @$prep_bind ]
212   ;
213 }
214
215
216 # savepoint syntax is the same as in Sybase ASE
217
218 sub _svp_begin {
219   my ($self, $name) = @_;
220
221   $self->_get_dbh->do("SAVE TRANSACTION $name");
222 }
223
224 # A new SAVE TRANSACTION with the same name releases the previous one.
225 sub _svp_release { 1 }
226
227 sub _svp_rollback {
228   my ($self, $name) = @_;
229
230   $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
231 }
232
233 sub build_datetime_parser {
234   my $self = shift;
235   my $type = "DateTime::Format::Strptime";
236   eval "use ${type}";
237   $self->throw_exception("Couldn't load ${type}: $@") if $@;
238   return $type->new( pattern => '%Y-%m-%d %H:%M:%S' );  # %F %T
239 }
240
241 sub sqlt_type { 'SQLServer' }
242
243 sub _sql_maker_opts {
244   my ( $self, $opts ) = @_;
245
246   if ( $opts ) {
247     $self->{_sql_maker_opts} = { %$opts };
248   }
249
250   return { limit_dialect => 'MSRowNumberOver', %{$self->{_sql_maker_opts}||{}} };
251 }
252
253 1;
254
255 =head1 NAME
256
257 DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
258 in DBIx::Class
259
260 =head1 SYNOPSIS
261
262 This is the base class for Microsoft SQL Server support, used by
263 L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
264 L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
265
266 =head1 IMPLEMENTATION NOTES
267
268 =head2 IDENTITY information
269
270 Microsoft SQL Server supports three methods of retrieving the IDENTITY
271 value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
272 SCOPE_IDENTITY is used here because it is the safest.  However, it must
273 be called is the same execute statement, not just the same connection.
274
275 So, this implementation appends a SELECT SCOPE_IDENTITY() statement
276 onto each INSERT to accommodate that requirement.
277
278 C<SELECT @@IDENTITY> can also be used by issuing:
279
280   $self->_identity_method('@@identity');
281
282 it will only be used if SCOPE_IDENTITY() fails.
283
284 This is more dangerous, as inserting into a table with an on insert trigger that
285 inserts into another table with an identity will give erroneous results on
286 recent versions of SQL Server.
287
288 =head2 identity insert
289
290 Be aware that we have tried to make things as simple as possible for our users.
291 For MSSQL that means that when a user tries to create a row, while supplying an
292 explicit value for an autoincrementing column, we will try to issue the
293 appropriate database call to make this possible, namely C<SET IDENTITY_INSERT
294 $table_name ON>. Unfortunately this operation in MSSQL requires the
295 C<db_ddladmin> privilege, which is normally not included in the standard
296 write-permissions.
297
298 =head1 AUTHOR
299
300 See L<DBIx::Class/CONTRIBUTORS>.
301
302 =head1 LICENSE
303
304 You may distribute this code under the same terms as Perl itself.
305
306 =cut