1 package DBIx::Class::Storage::DBI::MSSQL;
6 use base qw/DBIx::Class::Storage::DBI::AmbiguousGlob DBIx::Class::Storage::DBI/;
11 __PACKAGE__->mk_group_accessors(simple => qw/
12 _identity _identity_method
15 __PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MSSQL');
17 sub _set_identity_insert {
18 my ($self, $table) = @_;
21 'SET IDENTITY_INSERT %s ON',
22 $self->sql_maker->_quote ($table),
25 my $dbh = $self->_get_dbh;
26 eval { $dbh->do ($sql) };
28 $self->throw_exception (sprintf "Error executing '%s': %s",
35 sub _unset_identity_insert {
36 my ($self, $table) = @_;
39 'SET IDENTITY_INSERT %s OFF',
40 $self->sql_maker->_quote ($table),
43 my $dbh = $self->_get_dbh;
49 my ($source, $cols, $data) = @_;
51 my $is_identity_insert = (List::Util::first
52 { $source->column_info ($_)->{is_auto_increment} }
58 if ($is_identity_insert) {
59 $self->_set_identity_insert ($source->name);
62 $self->next::method(@_);
64 if ($is_identity_insert) {
65 $self->_unset_identity_insert ($source->name);
69 # support MSSQL GUID column types
73 my ($source, $to_insert) = @_;
75 my $supplied_col_info = $self->_resolve_column_info($source, [keys %$to_insert] );
78 my @pk_cols = $source->primary_columns;
80 @pk_cols{@pk_cols} = ();
83 $source->column_info($_)->{data_type}
85 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
88 my @auto_guids = grep {
89 $source->column_info($_)->{data_type}
91 $source->column_info($_)->{data_type} =~ /^uniqueidentifier/i
93 $source->column_info($_)->{auto_nextval}
94 } grep { not exists $pk_cols{$_} } $source->columns;
97 grep { not exists $to_insert->{$_} } (@pk_guids, @auto_guids);
99 my $updated_cols = {};
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;
106 my $is_identity_insert = (List::Util::first { $_->{is_auto_increment} } (values %$supplied_col_info) )
110 if ($is_identity_insert) {
111 $self->_set_identity_insert ($source->name);
114 $updated_cols = { %$updated_cols, %{ $self->next::method(@_) } };
116 if ($is_identity_insert) {
117 $self->_unset_identity_insert ($source->name);
121 return $updated_cols;
124 sub _prep_for_execute {
126 my ($op, $extra_bind, $ident, $args) = @_;
128 # cast MONEY values properly
129 if ($op eq 'insert' || $op eq 'update') {
130 my $fields = $args->[0];
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}
136 $ident->column_info ($col)->{data_type} =~ /^money\z/i) {
137 my $val = $fields->{$col};
138 $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
143 my ($sql, $bind) = $self->next::method (@_);
145 if ($op eq 'insert') {
146 $sql .= ';SELECT SCOPE_IDENTITY()';
150 return ($sql, $bind);
157 my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
159 if ($op eq 'insert') {
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 };
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
172 $self->_identity($identity);
176 return wantarray ? ($rv, $sth, @bind) : $rv;
179 sub last_insert_id { shift->_identity }
182 # MSSQL is retarded wrt ordered subselects. One needs to add a TOP 100%
183 # to *all* subqueries, do it here.
185 sub _select_args_to_query {
188 my ($sql, $prep_bind, @rest) = $self->next::method (@_);
190 # see if this is an ordered subquery
192 if ( scalar $self->sql_maker->_order_by_chunks ($attrs->{order_by}) ) {
193 $sql =~ s/^ \s* SELECT \s/SELECT TOP 100 PERCENT /xi;
197 ? ($sql, $prep_bind, @rest)
198 : \[ "($sql)", @$prep_bind ]
203 # savepoint syntax is the same as in Sybase ASE
206 my ($self, $name) = @_;
208 $self->_get_dbh->do("SAVE TRANSACTION $name");
211 # A new SAVE TRANSACTION with the same name releases the previous one.
212 sub _svp_release { 1 }
215 my ($self, $name) = @_;
217 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
220 sub build_datetime_parser {
222 my $type = "DateTime::Format::Strptime";
224 $self->throw_exception("Couldn't load ${type}: $@") if $@;
225 return $type->new( pattern => '%Y-%m-%d %H:%M:%S' ); # %F %T
228 sub sqlt_type { 'SQLServer' }
230 sub _get_mssql_version {
233 my $data = $self->_get_dbh->selectrow_hashref('xp_msver ProductVersion');
235 if ($data->{Character_Value} =~ /^(\d+)\./) {
238 $self->throw_exception(q{your MSSQL server doesn't have a version!});
242 sub _sql_maker_opts {
243 my ( $self, $opts ) = @_;
246 $self->{_sql_maker_opts} = { %$opts };
249 my $version = $self->_get_mssql_version;
252 limit_dialect => ($version >= 9 ? 'RowNumberOver' : 'Top'),
253 %{$self->{_sql_maker_opts}||{}}
261 DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
266 This is the base class for Microsoft SQL Server support, used by
267 L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
268 L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
270 =head1 IMPLEMENTATION NOTES
272 =head2 IDENTITY information
274 Microsoft SQL Server supports three methods of retrieving the IDENTITY
275 value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
276 SCOPE_IDENTITY is used here because it is the safest. However, it must
277 be called is the same execute statement, not just the same connection.
279 So, this implementation appends a SELECT SCOPE_IDENTITY() statement
280 onto each INSERT to accommodate that requirement.
282 C<SELECT @@IDENTITY> can also be used by issuing:
284 $self->_identity_method('@@identity');
286 it will only be used if SCOPE_IDENTITY() fails.
288 This is more dangerous, as inserting into a table with an on insert trigger that
289 inserts into another table with an identity will give erroneous results on
290 recent versions of SQL Server.
292 =head2 identity insert
294 Be aware that we have tried to make things as simple as possible for our users.
295 For MSSQL that means that when a user tries to create a row, while supplying an
296 explicit value for an autoincrementing column, we will try to issue the
297 appropriate database call to make this possible, namely C<SET IDENTITY_INSERT
298 $table_name ON>. Unfortunately this operation in MSSQL requires the
299 C<db_ddladmin> privilege, which is normally not included in the standard
304 See L<DBIx::Class/CONTRIBUTORS>.
308 You may distribute this code under the same terms as Perl itself.