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