Stop eating exceptions in ::Storage::DBI::DESTROY
[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
48fe9087 6use base qw/DBIx::Class::Storage::DBI::AmbiguousGlob DBIx::Class::Storage::DBI/;
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
5a77aa8b 17sub insert_bulk {
18 my $self = shift;
19 my ($source, $cols, $data) = @_;
20
21 my $identity_insert = 0;
22
23 COLUMNS:
24 foreach my $col (@{$cols}) {
25 if ($source->column_info($col)->{is_auto_increment}) {
26 $identity_insert = 1;
27 last COLUMNS;
28 }
29 }
30
31 if ($identity_insert) {
32 my $table = $source->from;
9ae966b9 33 $self->_get_dbh->do("SET IDENTITY_INSERT $table ON");
5a77aa8b 34 }
35
36 $self->next::method(@_);
37
38 if ($identity_insert) {
39 my $table = $source->from;
9ae966b9 40 $self->_get_dbh->do("SET IDENTITY_INSERT $table OFF");
5a77aa8b 41 }
42}
43
57ee81d0 44# support MSSQL GUID column types
45
ca791b95 46sub insert {
47 my $self = shift;
48 my ($source, $to_insert) = @_;
49
50 my $updated_cols = {};
51
52 my %guid_cols;
53 my @pk_cols = $source->primary_columns;
54 my %pk_cols;
55 @pk_cols{@pk_cols} = ();
56
57 my @pk_guids = grep {
be294d66 58 $source->column_info($_)->{data_type}
59 &&
ca791b95 60 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
61 } @pk_cols;
62
63 my @auto_guids = grep {
be294d66 64 $source->column_info($_)->{data_type}
65 &&
ca791b95 66 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
67 &&
68 $source->column_info($_)->{auto_nextval}
69 } grep { not exists $pk_cols{$_} } $source->columns;
70
71 my @get_guids_for =
72 grep { not exists $to_insert->{$_} } (@pk_guids, @auto_guids);
73
74 for my $guid_col (@get_guids_for) {
9ae966b9 75 my ($new_guid) = $self->_get_dbh->selectrow_array('SELECT NEWID()');
ca791b95 76 $updated_cols->{$guid_col} = $to_insert->{$guid_col} = $new_guid;
77 }
78
79 $updated_cols = { %$updated_cols, %{ $self->next::method(@_) } };
80
81 return $updated_cols;
82}
83
5a77aa8b 84sub _prep_for_execute {
85 my $self = shift;
86 my ($op, $extra_bind, $ident, $args) = @_;
87
88# cast MONEY values properly
89 if ($op eq 'insert' || $op eq 'update') {
90 my $fields = $args->[0];
5a77aa8b 91
92 for my $col (keys %$fields) {
1537084d 93 # $ident is a result source object with INSERT/UPDATE ops
be294d66 94 if ($ident->column_info ($col)->{data_type}
95 &&
96 $ident->column_info ($col)->{data_type} =~ /^money\z/i) {
5a77aa8b 97 my $val = $fields->{$col};
98 $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
99 }
100 }
101 }
102
103 my ($sql, $bind) = $self->next::method (@_);
104
105 if ($op eq 'insert') {
106 $sql .= ';SELECT SCOPE_IDENTITY()';
107
108 my $col_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]);
109 if (List::Util::first { $_->{is_auto_increment} } (values %$col_info) ) {
110
111 my $table = $ident->from;
112 my $identity_insert_on = "SET IDENTITY_INSERT $table ON";
113 my $identity_insert_off = "SET IDENTITY_INSERT $table OFF";
114 $sql = "$identity_insert_on; $sql; $identity_insert_off";
115 }
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
131 my ($identity) = $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
4c0f4206 149# savepoint syntax is the same as in Sybase ASE
150
151sub _svp_begin {
152 my ($self, $name) = @_;
153
9ae966b9 154 $self->_get_dbh->do("SAVE TRANSACTION $name");
4c0f4206 155}
156
157# A new SAVE TRANSACTION with the same name releases the previous one.
158sub _svp_release { 1 }
159
160sub _svp_rollback {
161 my ($self, $name) = @_;
162
9ae966b9 163 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
4c0f4206 164}
165
ed8de058 166sub build_datetime_parser {
167 my $self = shift;
168 my $type = "DateTime::Format::Strptime";
169 eval "use ${type}";
170 $self->throw_exception("Couldn't load ${type}: $@") if $@;
eb0323df 171 return $type->new( pattern => '%Y-%m-%d %H:%M:%S' ); # %F %T
172}
173
174sub sqlt_type { 'SQLServer' }
175
176sub _sql_maker_opts {
5a77aa8b 177 my ( $self, $opts ) = @_;
eb0323df 178
5a77aa8b 179 if ( $opts ) {
180 $self->{_sql_maker_opts} = { %$opts };
181 }
eb0323df 182
5a77aa8b 183 return { limit_dialect => 'Top', %{$self->{_sql_maker_opts}||{}} };
ed8de058 184}
3885cff6 185
75d07914 1861;
3885cff6 187
75d07914 188=head1 NAME
3885cff6 189
5a77aa8b 190DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
191in DBIx::Class
3885cff6 192
75d07914 193=head1 SYNOPSIS
3885cff6 194
5a77aa8b 195This is the base class for Microsoft SQL Server support, used by
196L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
197L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
eb0323df 198
5a77aa8b 199=head1 IMPLEMENTATION NOTES
eb0323df 200
fd05d10a 201=head2 IDENTITY information
202
5a77aa8b 203Microsoft SQL Server supports three methods of retrieving the IDENTITY
204value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
205SCOPE_IDENTITY is used here because it is the safest. However, it must
206be called is the same execute statement, not just the same connection.
eb0323df 207
5a77aa8b 208So, this implementation appends a SELECT SCOPE_IDENTITY() statement
209onto each INSERT to accommodate that requirement.
eb0323df 210
7b1b2582 211C<SELECT @@IDENTITY> can also be used by issuing:
212
213 $self->_identity_method('@@identity');
214
08cdc412 215it will only be used if SCOPE_IDENTITY() fails.
216
217This is more dangerous, as inserting into a table with an on insert trigger that
218inserts into another table with an identity will give erroneous results on
219recent versions of SQL Server.
7b1b2582 220
fd05d10a 221=head2 bulk_insert
222
223Be aware that we have tried to make things as simple as possible for our users.
224For MSSQL that means that when a user tries to do a populate/bulk_insert which
225includes an autoincrementing column, we will try to tell the database to allow
226the insertion of the autoinc column. But the user must have the db_ddladmin
227role membership, otherwise you will get a fairly opaque error message.
228
5a77aa8b 229=head1 AUTHOR
3885cff6 230
5a77aa8b 231See L<DBIx::Class/CONTRIBUTORS>.
3885cff6 232
75d07914 233=head1 LICENSE
3885cff6 234
75d07914 235You may distribute this code under the same terms as Perl itself.
3885cff6 236
75d07914 237=cut