better tests for "smalldatetime" support in MSSQL
[dbsrgits/DBIx-Class-Historic.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;
33 $self->dbh->do("SET IDENTITY_INSERT $table ON");
34 }
35
36 $self->next::method(@_);
37
38 if ($identity_insert) {
39 my $table = $source->from;
40 $self->dbh->do("SET IDENTITY_INSERT $table OFF");
41 }
42}
43
44sub _prep_for_execute {
45 my $self = shift;
46 my ($op, $extra_bind, $ident, $args) = @_;
47
48# cast MONEY values properly
49 if ($op eq 'insert' || $op eq 'update') {
50 my $fields = $args->[0];
51 my $col_info = $self->_resolve_column_info($ident, [keys %$fields]);
52
53 for my $col (keys %$fields) {
54 if ($col_info->{$col}{data_type} =~ /^money\z/i) {
55 my $val = $fields->{$col};
56 $fields->{$col} = \['CAST(? AS MONEY)', [ $col => $val ]];
57 }
58 }
59 }
60
61 my ($sql, $bind) = $self->next::method (@_);
62
63 if ($op eq 'insert') {
64 $sql .= ';SELECT SCOPE_IDENTITY()';
65
66 my $col_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]);
67 if (List::Util::first { $_->{is_auto_increment} } (values %$col_info) ) {
68
69 my $table = $ident->from;
70 my $identity_insert_on = "SET IDENTITY_INSERT $table ON";
71 my $identity_insert_off = "SET IDENTITY_INSERT $table OFF";
72 $sql = "$identity_insert_on; $sql; $identity_insert_off";
73 }
74 }
75
76 return ($sql, $bind);
77}
78
79sub _execute {
80 my $self = shift;
81 my ($op) = @_;
82
83 my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
84 if ($op eq 'insert') {
7b1b2582 85 $self->_identity($self->_fetch_identity($sth));
5a77aa8b 86 }
87
88 return wantarray ? ($rv, $sth, @bind) : $rv;
75d07914 89}
ed8de058 90
7b1b2582 91sub _fetch_identity {
92 my ($self, $sth) = @_;
93 my ($identity) = $sth->fetchrow_array;
94 $sth->finish;
95
96 if ((not defined $identity) && $self->_identity_method &&
97 $self->_identity_method eq '@@identity') {
98 ($identity) = $self->_dbh->selectrow_array('select @@identity');
99 }
100
101 return $identity;
102}
5a77aa8b 103
7b1b2582 104sub last_insert_id { shift->_identity }
5a77aa8b 105
4c0f4206 106# savepoint syntax is the same as in Sybase ASE
107
108sub _svp_begin {
109 my ($self, $name) = @_;
110
111 $self->dbh->do("SAVE TRANSACTION $name");
112}
113
114# A new SAVE TRANSACTION with the same name releases the previous one.
115sub _svp_release { 1 }
116
117sub _svp_rollback {
118 my ($self, $name) = @_;
119
120 $self->dbh->do("ROLLBACK TRANSACTION $name");
121}
122
ed8de058 123sub build_datetime_parser {
124 my $self = shift;
125 my $type = "DateTime::Format::Strptime";
126 eval "use ${type}";
127 $self->throw_exception("Couldn't load ${type}: $@") if $@;
eb0323df 128 return $type->new( pattern => '%Y-%m-%d %H:%M:%S' ); # %F %T
129}
130
131sub sqlt_type { 'SQLServer' }
132
133sub _sql_maker_opts {
5a77aa8b 134 my ( $self, $opts ) = @_;
eb0323df 135
5a77aa8b 136 if ( $opts ) {
137 $self->{_sql_maker_opts} = { %$opts };
138 }
eb0323df 139
5a77aa8b 140 return { limit_dialect => 'Top', %{$self->{_sql_maker_opts}||{}} };
ed8de058 141}
3885cff6 142
75d07914 1431;
3885cff6 144
75d07914 145=head1 NAME
3885cff6 146
5a77aa8b 147DBIx::Class::Storage::DBI::MSSQL - Base Class for Microsoft SQL Server support
148in DBIx::Class
3885cff6 149
75d07914 150=head1 SYNOPSIS
3885cff6 151
5a77aa8b 152This is the base class for Microsoft SQL Server support, used by
153L<DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server> and
154L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
eb0323df 155
5a77aa8b 156=head1 IMPLEMENTATION NOTES
eb0323df 157
5a77aa8b 158Microsoft SQL Server supports three methods of retrieving the IDENTITY
159value for inserted row: IDENT_CURRENT, @@IDENTITY, and SCOPE_IDENTITY().
160SCOPE_IDENTITY is used here because it is the safest. However, it must
161be called is the same execute statement, not just the same connection.
eb0323df 162
5a77aa8b 163So, this implementation appends a SELECT SCOPE_IDENTITY() statement
164onto each INSERT to accommodate that requirement.
eb0323df 165
7b1b2582 166C<SELECT @@IDENTITY> can also be used by issuing:
167
168 $self->_identity_method('@@identity');
169
08cdc412 170it will only be used if SCOPE_IDENTITY() fails.
171
172This is more dangerous, as inserting into a table with an on insert trigger that
173inserts into another table with an identity will give erroneous results on
174recent versions of SQL Server.
7b1b2582 175
5a77aa8b 176=head1 AUTHOR
3885cff6 177
5a77aa8b 178See L<DBIx::Class/CONTRIBUTORS>.
3885cff6 179
75d07914 180=head1 LICENSE
3885cff6 181
75d07914 182You may distribute this code under the same terms as Perl itself.
3885cff6 183
75d07914 184=cut