change CURRENT_TIMESTAMP to current_timestamp
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / DBI / MSSQL.pm
1 package DBIx::Class::Schema::Loader::DBI::MSSQL;
2
3 use strict;
4 use warnings;
5 use base 'DBIx::Class::Schema::Loader::DBI::Sybase::Common';
6 use Carp::Clan qw/^DBIx::Class/;
7 use Class::C3;
8
9 our $VERSION = '0.07000';
10
11 =head1 NAME
12
13 DBIx::Class::Schema::Loader::DBI::MSSQL - DBIx::Class::Schema::Loader::DBI MSSQL Implementation.
14
15 =head1 DESCRIPTION
16
17 Base driver for Microsoft SQL Server, used by
18 L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server> for support
19 via L<DBD::Sybase> and
20 L<DBIx::Class::Schema::Loader::DBI::ODBC::Microsoft_SQL_Server> for support via
21 L<DBD::ODBC>.
22
23 See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base> for
24 usage information.
25
26 =head1 CASE SENSITIVITY
27
28 Most MSSQL databases use C<CI> (case-insensitive) collation, for this reason
29 generated column names are lower-cased as this makes them easier to work with
30 in L<DBIx::Class>.
31
32 We attempt to detect the database collation at startup, and set the column
33 lowercasing behavior accordingly, as lower-cased column names do not work on
34 case-sensitive databases.
35
36 To manually control case-sensitive mode, put:
37
38     preserve_case => 1|0
39
40 in your Loader options.
41
42 See L<preserve_case|DBIx::Class::Schema::Loader::Base/preserve_case>.
43
44 B<NOTE:> this option used to be called C<case_sensitive_collation>, but has
45 been renamed to a more generic option.
46
47 =cut
48
49 sub _setup {
50     my $self = shift;
51
52     $self->next::method(@_);
53
54     return if defined $self->preserve_case;
55
56     my $dbh = $self->schema->storage->dbh;
57
58     # We use the sys.databases query for the general case, and fallback to
59     # databasepropertyex() if for some reason sys.databases is not available,
60     # which does not work over DBD::ODBC with unixODBC+FreeTDS.
61     #
62     # XXX why does databasepropertyex() not work over DBD::ODBC ?
63     #
64     # more on collations here: http://msdn.microsoft.com/en-us/library/ms143515.aspx
65     my ($collation_name) =
66            eval { $dbh->selectrow_array('SELECT collation_name FROM sys.databases WHERE name = DB_NAME()') }
67         || eval { $dbh->selectrow_array("SELECT CAST(databasepropertyex(DB_NAME(), 'Collation') AS VARCHAR)") };
68
69     if (not $collation_name) {
70         warn <<'EOF';
71
72 WARNING: MSSQL Collation detection failed. Defaulting to case-insensitive mode.
73 Override the 'preserve_case' attribute in your Loader options if needed.
74
75 See 'preserve_case' in
76 perldoc DBIx::Class::Schema::Loader::Base
77 EOF
78         $self->preserve_case(0);
79         return;
80     }
81
82     my $case_sensitive = $collation_name =~ /_(?:CS|BIN2?)(?:_|\z)/;
83
84     $self->preserve_case($case_sensitive ? 1 : 0);
85 }
86
87 sub _tables_list {
88     my ($self, $opts) = @_;
89
90     my $dbh = $self->schema->storage->dbh;
91     my $sth = $dbh->prepare(<<'EOF');
92 SELECT t.table_name
93 FROM INFORMATION_SCHEMA.TABLES t
94 WHERE lower(t.table_schema) = ?
95 EOF
96     $sth->execute(lc $self->db_schema);
97
98     my @tables = map @$_, @{ $sth->fetchall_arrayref };
99
100     return $self->_filter_tables(\@tables, $opts);
101 }
102
103 sub _table_pk_info {
104     my ($self, $table) = @_;
105     my $dbh = $self->schema->storage->dbh;
106     my $sth = $dbh->prepare(qq{sp_pkeys '$table'});
107     $sth->execute;
108
109     my @keydata;
110
111     while (my $row = $sth->fetchrow_hashref) {
112         push @keydata, $self->_lc($row->{COLUMN_NAME});
113     }
114
115     return \@keydata;
116 }
117
118 sub _table_fk_info {
119     my ($self, $table) = @_;
120
121     my ($local_cols, $remote_cols, $remote_table, @rels, $sth);
122     my $dbh = $self->schema->storage->dbh;
123     eval {
124         $sth = $dbh->prepare(qq{sp_fkeys \@fktable_name = '$table'});
125         $sth->execute;
126     };
127
128     while (my $row = eval { $sth->fetchrow_hashref }) {
129         my $fk = $row->{FK_NAME};
130         push @{$local_cols->{$fk}}, $self->_lc($row->{FKCOLUMN_NAME});
131         push @{$remote_cols->{$fk}}, $self->_lc($row->{PKCOLUMN_NAME});
132         $remote_table->{$fk} = $row->{PKTABLE_NAME};
133     }
134
135     foreach my $fk (keys %$remote_table) {
136         push @rels, {
137                       local_columns => \@{$local_cols->{$fk}},
138                       remote_columns => \@{$remote_cols->{$fk}},
139                       remote_table => $remote_table->{$fk},
140                     };
141
142     }
143     return \@rels;
144 }
145
146 sub _table_uniq_info {
147     my ($self, $table) = @_;
148
149     my $dbh = $self->schema->storage->dbh;
150     local $dbh->{FetchHashKeyName} = 'NAME_lc';
151
152     my $sth = $dbh->prepare(qq{
153 SELECT ccu.constraint_name, ccu.column_name
154 FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu
155 JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc on (ccu.constraint_name = tc.constraint_name)
156 JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu on (ccu.constraint_name = kcu.constraint_name and ccu.column_name = kcu.column_name)
157 wHERE lower(ccu.table_name) = @{[ $dbh->quote(lc $table) ]} AND constraint_type = 'UNIQUE' ORDER BY kcu.ordinal_position
158     });
159     $sth->execute;
160     my $constraints;
161     while (my $row = $sth->fetchrow_hashref) {
162         my $name = $row->{constraint_name};
163         my $col  = $self->_lc($row->{column_name});
164         push @{$constraints->{$name}}, $col;
165     }
166
167     my @uniqs = map { [ $_ => $constraints->{$_} ] } keys %$constraints;
168     return \@uniqs;
169 }
170
171 sub _columns_info_for {
172     my $self    = shift;
173     my ($table) = @_;
174
175     my $result = $self->next::method(@_);
176
177     while (my ($col, $info) = each %$result) {
178         my $dbh = $self->schema->storage->dbh;
179
180 # find identities
181         my $sth = $dbh->prepare(qq{
182 SELECT column_name 
183 FROM INFORMATION_SCHEMA.COLUMNS
184 WHERE columnproperty(object_id(@{[ $dbh->quote(lc $table) ]}, 'U'), @{[ $dbh->quote(lc $col) ]}, 'IsIdentity') = 1
185 AND lower(table_name) = @{[ $dbh->quote(lc $table) ]} AND lower(column_name) = @{[ $dbh->quote(lc $col) ]}
186         });
187         if (eval { $sth->execute; $sth->fetchrow_array }) {
188             $info->{is_auto_increment} = 1;
189             $info->{data_type} =~ s/\s*identity//i;
190             delete $info->{size};
191         }
192
193 # fix types
194         if ($info->{data_type} eq 'int') {
195             $info->{data_type} = 'integer';
196         }
197         elsif ($info->{data_type} eq 'timestamp') {
198             $info->{inflate_datetime} = 0;
199         }
200         elsif ($info->{data_type} =~ /^(?:numeric|decimal)\z/) {
201             if (ref($info->{size}) && $info->{size}[0] == 18 && $info->{size}[1] == 0) {
202                 delete $info->{size};
203             }
204         }
205         elsif ($info->{data_type} eq 'real') {
206             $info->{data_type} = 'float';
207             $info->{size}      = 24;
208         }
209         elsif ($info->{data_type} eq 'float') {
210             $info->{data_type} = 'double precision';
211         }
212         elsif ($info->{data_type} =~ /^(?:small)?datetime\z/) {
213             # fixup for DBD::Sybase
214             if ($info->{default_value} && $info->{default_value} eq '3') {
215                 delete $info->{default_value};
216             }
217         }
218         elsif ($info->{data_type} eq 'datetimeoffset') {
219             $info->{size} = {
220                 26 => 0,
221                 28 => 1,
222                 29 => 2,
223                 30 => 3,
224                 31 => 4,
225                 32 => 5,
226                 33 => 6,
227                 34 => 7,
228             }->{$info->{size}};
229
230             delete $info->{size} if $info->{size} == 7;
231         }
232         elsif ($info->{data_type} eq 'datetime2') {
233             $info->{size} = {
234                 19 => 0,
235                 21 => 1,
236                 22 => 2,
237                 23 => 3,
238                 24 => 4,
239                 25 => 5,
240                 26 => 6,
241                 27 => 7,
242             }->{$info->{size}};
243
244             delete $info->{size} if $info->{size} == 7;
245         }
246         elsif ($info->{data_type} eq 'time') {
247             $info->{size} = {
248                  8 => 0,
249                 10 => 1,
250                 11 => 2,
251                 12 => 3,
252                 13 => 4,
253                 14 => 5,
254                 15 => 6,
255                 16 => 7,
256             }->{$info->{size}};
257
258             delete $info->{size} if $info->{size} == 7;
259         }
260
261         if ($info->{data_type} !~ /^(?:n?char|n?varchar|binary|varbinary|numeric|decimal|float|datetime(?:2|offset)|time)\z/) {
262             delete $info->{size};
263         }
264
265 # get default
266         $sth = $dbh->prepare(qq{
267 SELECT column_default
268 FROM INFORMATION_SCHEMA.COLUMNS
269 wHERE lower(table_name) = @{[ $dbh->quote(lc $table) ]} AND lower(column_name) = @{[ $dbh->quote(lc $col) ]}
270         });
271         my ($default) = eval { $sth->execute; $sth->fetchrow_array };
272
273         if (defined $default) {
274             # strip parens
275             $default =~ s/^\( (.*) \)\z/$1/x;
276
277             # Literal strings are in ''s, numbers are in ()s (in some versions of
278             # MSSQL, in others they are unquoted) everything else is a function.
279             $info->{default_value} =
280                 $default =~ /^['(] (.*) [)']\z/x ? $1 :
281                     $default =~ /^\d/ ? $default : \$default;
282
283             if (eval { lc ${ $info->{default_value} } }||'' eq 'getdate()') {
284                 ${ $info->{default_value} } = 'current_timestamp';
285             }
286         }
287     }
288
289     return $result;
290 }
291
292 =head1 SEE ALSO
293
294 L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server>,
295 L<DBIx::Class::Schema::Loader::DBI::ODBC::Microsoft_SQL_Server>,
296 L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>,
297 L<DBIx::Class::Schema::Loader::DBI>
298
299 =head1 AUTHOR
300
301 See L<DBIx::Class::Schema::Loader/AUTHOR> and L<DBIx::Class::Schema::Loader/CONTRIBUTORS>.
302
303 =head1 LICENSE
304
305 This library is free software; you can redistribute it and/or modify it under
306 the same terms as Perl itself.
307
308 =cut
309
310 1;
311 # vim:et sts=4 sw=4 tw=0: