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