Grrrr
[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;
86e68d2d 33 $self->last_dbh->do("SET IDENTITY_INSERT $table ON");
5a77aa8b 34 }
35
36 $self->next::method(@_);
37
38 if ($identity_insert) {
39 my $table = $source->from;
86e68d2d 40 $self->last_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 {
58 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
59 } @pk_cols;
60
61 my @auto_guids = grep {
62 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
63 &&
64 $source->column_info($_)->{auto_nextval}
65 } grep { not exists $pk_cols{$_} } $source->columns;
66
67 my @get_guids_for =
68 grep { not exists $to_insert->{$_} } (@pk_guids, @auto_guids);
69
70 for my $guid_col (@get_guids_for) {
86e68d2d 71 my ($new_guid) = $self->last_dbh->selectrow_array('SELECT NEWID()');
ca791b95 72 $updated_cols->{$guid_col} = $to_insert->{$guid_col} = $new_guid;
73 }
74
75 $updated_cols = { %$updated_cols, %{ $self->next::method(@_) } };
76
77 return $updated_cols;
78}
79
5a77aa8b 80sub _prep_for_execute {
81 my $self = shift;
82 my ($op, $extra_bind, $ident, $args) = @_;
83
84# cast MONEY values properly
85 if ($op eq 'insert' || $op eq 'update') {
86 my $fields = $args->[0];
5a77aa8b 87
88 for my $col (keys %$fields) {
1537084d 89 # $ident is a result source object with INSERT/UPDATE ops
90 if ($ident->column_info ($col)->{data_type} =~ /^money\z/i) {
5a77aa8b 91 my $val = $fields->{$col};
92 $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
93 }
94 }
95 }
96
97 my ($sql, $bind) = $self->next::method (@_);
98
99 if ($op eq 'insert') {
100 $sql .= ';SELECT SCOPE_IDENTITY()';
101
102 my $col_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]);
103 if (List::Util::first { $_->{is_auto_increment} } (values %$col_info) ) {
104
105 my $table = $ident->from;
106 my $identity_insert_on = "SET IDENTITY_INSERT $table ON";
107 my $identity_insert_off = "SET IDENTITY_INSERT $table OFF";
108 $sql = "$identity_insert_on; $sql; $identity_insert_off";
109 }
110 }
111
112 return ($sql, $bind);
113}
114
115sub _execute {
116 my $self = shift;
117 my ($op) = @_;
118
119 my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
1537084d 120
5a77aa8b 121 if ($op eq 'insert') {
5a77aa8b 122
1537084d 123 # this should bring back the result of SELECT SCOPE_IDENTITY() we tacked
124 # on in _prep_for_execute above
125 my ($identity) = $sth->fetchrow_array;
ed8de058 126
1537084d 127 # SCOPE_IDENTITY failed, but we can do something else
128 if ( (! $identity) && $self->_identity_method) {
129 ($identity) = $self->_dbh->selectrow_array(
130 'select ' . $self->_identity_method
131 );
132 }
7b1b2582 133
1537084d 134 $self->_identity($identity);
135 $sth->finish;
7b1b2582 136 }
137
1537084d 138 return wantarray ? ($rv, $sth, @bind) : $rv;
7b1b2582 139}
5a77aa8b 140
7b1b2582 141sub last_insert_id { shift->_identity }
5a77aa8b 142
4c0f4206 143# savepoint syntax is the same as in Sybase ASE
144
145sub _svp_begin {
146 my ($self, $name) = @_;
147
86e68d2d 148 $self->last_dbh->do("SAVE TRANSACTION $name");
4c0f4206 149}
150
151# A new SAVE TRANSACTION with the same name releases the previous one.
152sub _svp_release { 1 }
153
154sub _svp_rollback {
155 my ($self, $name) = @_;
156
86e68d2d 157 $self->last_dbh->do("ROLLBACK TRANSACTION $name");
4c0f4206 158}
159
ed8de058 160sub build_datetime_parser {
161 my $self = shift;
162 my $type = "DateTime::Format::Strptime";
163 eval "use ${type}";
164 $self->throw_exception("Couldn't load ${type}: $@") if $@;
eb0323df 165 return $type->new( pattern => '%Y-%m-%d %H:%M:%S' ); # %F %T
166}
167
168sub sqlt_type { 'SQLServer' }
169
170sub _sql_maker_opts {
5a77aa8b 171 my ( $self, $opts ) = @_;
eb0323df 172
5a77aa8b 173 if ( $opts ) {
174 $self->{_sql_maker_opts} = { %$opts };
175 }
eb0323df 176
5a77aa8b 177 return { limit_dialect => 'Top', %{$self->{_sql_maker_opts}||{}} };
ed8de058 178}
3885cff6 179
75d07914 1801;
3885cff6 181
75d07914 182=head1 NAME
3885cff6 183
5a77aa8b 184DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
185in DBIx::Class
3885cff6 186
75d07914 187=head1 SYNOPSIS
3885cff6 188
5a77aa8b 189This is the base class for Microsoft SQL Server support, used by
190L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
191L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
eb0323df 192
5a77aa8b 193=head1 IMPLEMENTATION NOTES
eb0323df 194
5a77aa8b 195Microsoft SQL Server supports three methods of retrieving the IDENTITY
196value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
197SCOPE_IDENTITY is used here because it is the safest. However, it must
198be called is the same execute statement, not just the same connection.
eb0323df 199
5a77aa8b 200So, this implementation appends a SELECT SCOPE_IDENTITY() statement
201onto each INSERT to accommodate that requirement.
eb0323df 202
7b1b2582 203C<SELECT @@IDENTITY> can also be used by issuing:
204
205 $self->_identity_method('@@identity');
206
08cdc412 207it will only be used if SCOPE_IDENTITY() fails.
208
209This is more dangerous, as inserting into a table with an on insert trigger that
210inserts into another table with an identity will give erroneous results on
211recent versions of SQL Server.
7b1b2582 212
5a77aa8b 213=head1 AUTHOR
3885cff6 214
5a77aa8b 215See L<DBIx::Class/CONTRIBUTORS>.
3885cff6 216
75d07914 217=head1 LICENSE
3885cff6 218
75d07914 219You may distribute this code under the same terms as Perl itself.
3885cff6 220
75d07914 221=cut