Attempt to fix 'Attempt to free unreferenced scalar' on 5.8
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / DBI / MSSQL.pm
CommitLineData
fe67d343 1package DBIx::Class::Schema::Loader::DBI::MSSQL;
2
3use strict;
4use warnings;
de82711a 5use base 'DBIx::Class::Schema::Loader::DBI::Sybase::Common';
942bd5e0 6use mro 'c3';
afcd3c32 7use Try::Tiny;
ecf22f0a 8use List::Util 'any';
c4a69b87 9use DBIx::Class::Schema::Loader::Table::Sybase ();
dbe5c904 10use namespace::clean;
c4a69b87 11
306bf770 12our $VERSION = '0.07047';
fe67d343 13
14=head1 NAME
15
16DBIx::Class::Schema::Loader::DBI::MSSQL - DBIx::Class::Schema::Loader::DBI MSSQL Implementation.
17
acfcc1fb 18=head1 DESCRIPTION
fe67d343 19
acfcc1fb 20Base driver for Microsoft SQL Server, used by
21L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server> for support
22via L<DBD::Sybase> and
23L<DBIx::Class::Schema::Loader::DBI::ODBC::Microsoft_SQL_Server> for support via
24L<DBD::ODBC>.
fe67d343 25
acfcc1fb 26See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base> for
27usage information.
fe67d343 28
b065e3df 29=head1 CASE SENSITIVITY
30
31Most MSSQL databases use C<CI> (case-insensitive) collation, for this reason
32generated column names are lower-cased as this makes them easier to work with
33in L<DBIx::Class>.
34
c4a69b87 35We attempt to detect the database collation at startup for any database
36included in L<db_schema|DBIx::Class::Schema::Loader::Base/db_schema>, and set
37the column lowercasing behavior accordingly, as lower-cased column names do not
38work on case-sensitive databases.
b065e3df 39
81ade4d9 40To manually control case-sensitive mode, put:
b065e3df 41
bc1cb85e 42 preserve_case => 1|0
b065e3df 43
103e90da 44in your Loader options.
b065e3df 45
bc1cb85e 46See L<preserve_case|DBIx::Class::Schema::Loader::Base/preserve_case>.
fe67d343 47
bc1cb85e 48B<NOTE:> this option used to be called C<case_sensitive_collation>, but has
49been renamed to a more generic option.
3559ae79 50
bc1cb85e 51=cut
3559ae79 52
7b2db7f3 53# SQL Server 2000: Ancient as time itself, but still out in the wild
54sub _is_2k {
55 return shift->schema->storage->_server_info->{normalized_dbms_version} < 9;
56}
57
c4a69b87 58sub _system_databases {
59 return (qw/
60 master model tempdb msdb
61 /);
62}
63
64sub _system_tables {
65 return (qw/
66 spt_fallback_db spt_fallback_dev spt_fallback_usg spt_monitor spt_values MSreplication_options
67 /);
68}
69
7b2db7f3 70sub _schemas {
c4a69b87 71 my ($self, $db) = @_;
72
7b2db7f3 73 my $owners = $self->dbh->selectcol_arrayref($self->_is_2k ? <<"EOF2K" : <<"EOF");
c4a69b87 74SELECT name
75FROM [$db].dbo.sysusers
76WHERE uid <> gid
7b2db7f3 77EOF2K
78SELECT name
79FROM [$db].sys.schemas
c4a69b87 80EOF
81
82 return grep !/^(?:#|guest|INFORMATION_SCHEMA|sys)/, @$owners;
83}
84
7b2db7f3 85sub _current_schema {
86 my $self = shift;
87
88 if ($self->_is_2k) {
89 return ($self->dbh->selectrow_array('SELECT user_name()'))[0];
90 }
91
92 return ($self->dbh->selectrow_array('SELECT schema_name()'))[0];
93}
94
116431d6 95sub _current_db {
96 my $self = shift;
97 return ($self->dbh->selectrow_array('SELECT db_name()'))[0];
98}
99
100sub _switch_db {
101 my ($self, $db) = @_;
102 $self->dbh->do("use [$db]");
103}
104
3559ae79 105sub _setup {
106 my $self = shift;
107
bc1cb85e 108 $self->next::method(@_);
3559ae79 109
116431d6 110 my $current_db = $self->_current_db;
b065e3df 111
c4a69b87 112 if (ref $self->db_schema eq 'HASH') {
113 if (keys %{ $self->db_schema } < 2) {
114 my ($db) = keys %{ $self->db_schema };
3559ae79 115
c4a69b87 116 $db ||= $current_db;
b7a0a040 117
c4a69b87 118 if ($db eq '%') {
119 my $owners = $self->db_schema->{$db};
b065e3df 120
c4a69b87 121 my $db_names = $self->dbh->selectcol_arrayref(<<'EOF');
122SELECT name
123FROM master.dbo.sysdatabases
124EOF
bc1cb85e 125
c4a69b87 126 my @dbs;
127
128 foreach my $db_name (@$db_names) {
129 push @dbs, $db_name
130 unless any { $_ eq $db_name } $self->_system_databases;
131 }
132
133 $self->db_schema({});
134
135 DB: foreach my $db (@dbs) {
136 if (not ((ref $owners eq 'ARRAY' && $owners->[0] eq '%') || $owners eq '%')) {
137 my @owners;
138
139 foreach my $owner (@$owners) {
140 push @owners, $owner
141 if $self->dbh->selectrow_array(<<"EOF");
142SELECT name
143FROM [$db].dbo.sysusers
144WHERE name = @{[ $self->dbh->quote($owner) ]}
b065e3df 145EOF
c4a69b87 146 }
147
148 next DB unless @owners;
149
150 $self->db_schema->{$db} = \@owners;
151 }
152 else {
153 # for post-processing below
154 $self->db_schema->{$db} = '%';
155 }
156 }
157
158 $self->qualify_objects(1);
159 }
160 else {
161 if ($db ne $current_db) {
7b2db7f3 162 $self->_switch_db($db);
c4a69b87 163
164 $self->qualify_objects(1);
165 }
166 }
167 }
168 else {
169 $self->qualify_objects(1);
170 }
171 }
172 elsif (ref $self->db_schema eq 'ARRAY' || (not defined $self->db_schema)) {
173 my $owners = $self->db_schema;
7b2db7f3 174 $owners ||= [ $self->_current_schema ];
c4a69b87 175
176 $self->qualify_objects(1) if @$owners > 1;
177
178 $self->db_schema({ $current_db => $owners });
b7a0a040 179 }
3559ae79 180
c4a69b87 181 foreach my $db (keys %{ $self->db_schema }) {
182 if ($self->db_schema->{$db} eq '%') {
7b2db7f3 183 $self->db_schema->{$db} = [ $self->_schemas($db) ];
c4a69b87 184
185 $self->qualify_objects(1);
186 }
187 }
3559ae79 188
c4a69b87 189 if (not defined $self->preserve_case) {
190 foreach my $db (keys %{ $self->db_schema }) {
191 # We use the sys.databases query for the general case, and fallback to
192 # databasepropertyex() if for some reason sys.databases is not available,
193 # which does not work over DBD::ODBC with unixODBC+FreeTDS.
194 #
195 # XXX why does databasepropertyex() not work over DBD::ODBC ?
196 #
197 # more on collations here: http://msdn.microsoft.com/en-us/library/ms143515.aspx
116431d6 198
199 my $current_db = $self->_current_db;
200
201 $self->_switch_db($db);
202
203 my $collation_name =
204 (eval { $self->dbh->selectrow_array("SELECT collation_name FROM [$db].sys.databases WHERE name = @{[ $self->dbh->quote($db) ]}") })[0]
205 || (eval { $self->dbh->selectrow_array("SELECT CAST(databasepropertyex(@{[ $self->dbh->quote($db) ]}, 'Collation') AS VARCHAR)") })[0];
206
207 $self->_switch_db($current_db);
c4a69b87 208
209 if (not $collation_name) {
210 warn <<"EOF";
211
212WARNING: MSSQL Collation detection failed for database '$db'. Defaulting to
213case-insensitive mode. Override the 'preserve_case' attribute in your Loader
214options if needed.
215
216See 'preserve_case' in
217perldoc DBIx::Class::Schema::Loader::Base
218EOF
219 $self->preserve_case(0) unless $self->preserve_case;
220 }
221 else {
222 my $case_sensitive = $collation_name =~ /_(?:CS|BIN2?)(?:_|\z)/;
223
224 if ($case_sensitive && (not $self->preserve_case)) {
225 $self->preserve_case(1);
226 }
227 else {
228 $self->preserve_case(0);
229 }
230 }
231 }
232 }
3559ae79 233}
234
acfcc1fb 235sub _tables_list {
5784b2b9 236 my ($self) = @_;
fe67d343 237
c4a69b87 238 my @tables;
239
240 while (my ($db, $owners) = each %{ $self->db_schema }) {
241 foreach my $owner (@$owners) {
242 my $table_names = $self->dbh->selectcol_arrayref(<<"EOF");
243SELECT table_name
244FROM [$db].INFORMATION_SCHEMA.TABLES
245WHERE table_schema = @{[ $self->dbh->quote($owner) ]}
acfcc1fb 246EOF
fe67d343 247
c4a69b87 248 TABLE: foreach my $table_name (@$table_names) {
249 next TABLE if any { $_ eq $table_name } $self->_system_tables;
250
251 push @tables, DBIx::Class::Schema::Loader::Table::Sybase->new(
252 loader => $self,
253 name => $table_name,
254 database => $db,
255 schema => $owner,
256 );
257 }
258 }
259 }
acfcc1fb 260
5784b2b9 261 return $self->_filter_tables(\@tables);
acfcc1fb 262}
fe67d343 263
fe67d343 264sub _table_pk_info {
265 my ($self, $table) = @_;
fe67d343 266
c4a69b87 267 my $db = $table->database;
268
116431d6 269 my $pk = $self->dbh->selectcol_arrayref(<<"EOF");
c4a69b87 270SELECT kcu.column_name
271FROM [$db].INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
272JOIN [$db].INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
273 ON kcu.table_name = tc.table_name
274 AND kcu.table_schema = tc.table_schema
275 AND kcu.constraint_name = tc.constraint_name
276WHERE tc.table_name = @{[ $self->dbh->quote($table->name) ]}
277 AND tc.table_schema = @{[ $self->dbh->quote($table->schema) ]}
278 AND tc.constraint_type = 'PRIMARY KEY'
279ORDER BY kcu.ordinal_position
280EOF
116431d6 281
282 $pk = [ map $self->_lc($_), @$pk ];
283
284 return $pk;
fe67d343 285}
286
287sub _table_fk_info {
288 my ($self, $table) = @_;
289
c4a69b87 290 my $db = $table->database;
291
292 my $sth = $self->dbh->prepare(<<"EOF");
f8640ecc 293SELECT rc.constraint_name, rc.unique_constraint_schema, uk_tc.table_name,
294 fk_kcu.column_name, uk_kcu.column_name, rc.delete_rule, rc.update_rule
c4a69b87 295FROM [$db].INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk_tc
296JOIN [$db].INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
297 ON rc.constraint_name = fk_tc.constraint_name
298 AND rc.constraint_schema = fk_tc.table_schema
299JOIN [$db].INFORMATION_SCHEMA.KEY_COLUMN_USAGE fk_kcu
300 ON fk_kcu.constraint_name = fk_tc.constraint_name
301 AND fk_kcu.table_name = fk_tc.table_name
494e0205 302 AND fk_kcu.table_schema = fk_tc.table_schema
c4a69b87 303JOIN [$db].INFORMATION_SCHEMA.TABLE_CONSTRAINTS uk_tc
304 ON uk_tc.constraint_name = rc.unique_constraint_name
305 AND uk_tc.table_schema = rc.unique_constraint_schema
306JOIN [$db].INFORMATION_SCHEMA.KEY_COLUMN_USAGE uk_kcu
307 ON uk_kcu.constraint_name = rc.unique_constraint_name
308 AND uk_kcu.ordinal_position = fk_kcu.ordinal_position
309 AND uk_kcu.table_name = uk_tc.table_name
310 AND uk_kcu.table_schema = rc.unique_constraint_schema
311WHERE fk_tc.table_name = @{[ $self->dbh->quote($table->name) ]}
312 AND fk_tc.table_schema = @{[ $self->dbh->quote($table->schema) ]}
313ORDER BY fk_kcu.ordinal_position
314EOF
fe67d343 315
c4a69b87 316 $sth->execute;
fe67d343 317
c4a69b87 318 my %rels;
319
f8640ecc 320 while (my ($fk, $remote_schema, $remote_table, $col, $remote_col,
321 $delete_rule, $update_rule) = $sth->fetchrow_array) {
116431d6 322 push @{ $rels{$fk}{local_columns} }, $self->_lc($col);
323 push @{ $rels{$fk}{remote_columns} }, $self->_lc($remote_col);
494e0205 324
c4a69b87 325 $rels{$fk}{remote_table} = DBIx::Class::Schema::Loader::Table::Sybase->new(
326 loader => $self,
327 name => $remote_table,
328 database => $db,
329 schema => $remote_schema,
330 ) unless exists $rels{$fk}{remote_table};
f8640ecc 331
332 $rels{$fk}{attrs} ||= {
333 on_delete => uc $delete_rule,
334 on_update => uc $update_rule,
335 is_deferrable => 1 # constraints can be temporarily disabled, but DEFERRABLE is not supported
336 };
fe67d343 337 }
c4a69b87 338
339 return [ values %rels ];
fe67d343 340}
341
342sub _table_uniq_info {
343 my ($self, $table) = @_;
344
c4a69b87 345 my $db = $table->database;
346
347 my $sth = $self->dbh->prepare(<<"EOF");
348SELECT tc.constraint_name, kcu.column_name
349FROM [$db].INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
350JOIN [$db].INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
351 ON kcu.constraint_name = tc.constraint_name
352 AND kcu.table_name = tc.table_name
353 AND kcu.table_schema = tc.table_schema
354wHERE tc.table_name = @{[ $self->dbh->quote($table->name) ]}
355 AND tc.table_schema = @{[ $self->dbh->quote($table->schema) ]}
356 AND tc.constraint_type = 'UNIQUE'
357ORDER BY kcu.ordinal_position
358EOF
020f3c3a 359
fe67d343 360 $sth->execute;
c4a69b87 361
362 my %uniq;
363
364 while (my ($constr, $col) = $sth->fetchrow_array) {
365 push @{ $uniq{$constr} }, $self->_lc($col);
fe67d343 366 }
367
6c4f5a4a 368 return [ map [ $_ => $uniq{$_} ], sort keys %uniq ];
fe67d343 369}
370
9c9197d6 371sub _columns_info_for {
372 my $self = shift;
373 my ($table) = @_;
374
c4a69b87 375 my $db = $table->database;
9c9197d6 376
c4a69b87 377 my $result = $self->next::method(@_);
afcd3c32 378
b8aba8da 379 # get type info (and identity)
7b2db7f3 380 my $rows = $self->dbh->selectall_arrayref($self->_is_2k ? <<"EOF2K" : <<"EOF");
b8aba8da 381SELECT c.column_name, c.character_maximum_length, c.data_type, c.datetime_precision, c.column_default, (sc.status & 0x80) is_identity
382FROM [$db].INFORMATION_SCHEMA.COLUMNS c
383JOIN [$db].dbo.sysusers ss ON
384 c.table_schema = ss.name
385JOIN [$db].dbo.sysobjects so ON
386 c.table_name = so.name
387 AND so.uid = ss.uid
388JOIN [$db].dbo.syscolumns sc ON
389 c.column_name = sc.name
390 AND sc.id = so.Id
391WHERE c.table_schema = @{[ $self->dbh->quote($table->schema) ]}
392 AND c.table_name = @{[ $self->dbh->quote($table->name) ]}
393EOF2K
394SELECT c.column_name, c.character_maximum_length, c.data_type, c.datetime_precision, c.column_default, sc.is_identity
395FROM [$db].INFORMATION_SCHEMA.COLUMNS c
396JOIN [$db].sys.schemas ss ON
397 c.table_schema = ss.name
398JOIN [$db].sys.objects so ON
399 c.table_name = so.name
400 AND so.schema_id = ss.schema_id
401JOIN [$db].sys.columns sc ON
402 c.column_name = sc.name
403 AND sc.object_id = so.object_id
404WHERE c.table_schema = @{[ $self->dbh->quote($table->schema) ]}
405 AND c.table_name = @{[ $self->dbh->quote($table->name) ]}
c4a69b87 406EOF
afcd3c32 407
b8aba8da 408 foreach my $row (@$rows) {
409 my ($col, $char_max_length, $data_type, $datetime_precision, $default, $is_identity) = @$row;
410 $col = lc $col unless $self->preserve_case;
411 my $info = $result->{$col} || next;
412
afcd3c32 413 $info->{data_type} = $data_type;
414
415 if (defined $char_max_length) {
416 $info->{size} = $char_max_length;
417 $info->{size} = 0 if $char_max_length < 0;
418 }
020f3c3a 419
c4a69b87 420 if ($is_identity) {
9c9197d6 421 $info->{is_auto_increment} = 1;
422 $info->{data_type} =~ s/\s*identity//i;
423 delete $info->{size};
424 }
fe67d343 425
b8aba8da 426 # fix types
afcd3c32 427 if ($data_type eq 'int') {
81ade4d9 428 $info->{data_type} = 'integer';
429 }
afcd3c32 430 elsif ($data_type eq 'timestamp') {
81ade4d9 431 $info->{inflate_datetime} = 0;
432 }
afcd3c32 433 elsif ($data_type =~ /^(?:numeric|decimal)\z/) {
81ade4d9 434 if (ref($info->{size}) && $info->{size}[0] == 18 && $info->{size}[1] == 0) {
435 delete $info->{size};
436 }
437 }
afcd3c32 438 elsif ($data_type eq 'float') {
81ade4d9 439 $info->{data_type} = 'double precision';
afcd3c32 440 delete $info->{size};
81ade4d9 441 }
afcd3c32 442 elsif ($data_type =~ /^(?:small)?datetime\z/) {
81ade4d9 443 # fixup for DBD::Sybase
444 if ($info->{default_value} && $info->{default_value} eq '3') {
445 delete $info->{default_value};
446 }
447 }
afcd3c32 448 elsif ($data_type =~ /^(?:datetime(?:2|offset)|time)\z/) {
ae38ed69 449 $info->{size} = $datetime_precision;
81ade4d9 450
451 delete $info->{size} if $info->{size} == 7;
452 }
afcd3c32 453 elsif ($data_type eq 'varchar' && $info->{size} == 0) {
ae38ed69 454 $info->{data_type} = 'text';
455 delete $info->{size};
81ade4d9 456 }
afcd3c32 457 elsif ($data_type eq 'nvarchar' && $info->{size} == 0) {
ae38ed69 458 $info->{data_type} = 'ntext';
459 delete $info->{size};
460 }
afcd3c32 461 elsif ($data_type eq 'varbinary' && $info->{size} == 0) {
ae38ed69 462 $info->{data_type} = 'image';
463 delete $info->{size};
81ade4d9 464 }
465
afcd3c32 466 if ($data_type !~ /^(?:n?char|n?varchar|binary|varbinary|numeric|decimal|float|datetime(?:2|offset)|time)\z/) {
81ade4d9 467 delete $info->{size};
468 }
469
9c9197d6 470 if (defined $default) {
471 # strip parens
472 $default =~ s/^\( (.*) \)\z/$1/x;
473
474 # Literal strings are in ''s, numbers are in ()s (in some versions of
475 # MSSQL, in others they are unquoted) everything else is a function.
476 $info->{default_value} =
477 $default =~ /^['(] (.*) [)']\z/x ? $1 :
478 $default =~ /^\d/ ? $default : \$default;
8a64178e 479
268cc246 480 if ((eval { lc ${ $info->{default_value} } }||'') eq 'getdate()') {
6e566cc4 481 ${ $info->{default_value} } = 'current_timestamp';
701cd3e3 482
483 my $getdate = 'getdate()';
484 $info->{original}{default_value} = \$getdate;
8a64178e 485 }
9c9197d6 486 }
5c6fb0a1 487 }
488
9c9197d6 489 return $result;
fe67d343 490}
491
fe67d343 492=head1 SEE ALSO
493
acfcc1fb 494L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server>,
495L<DBIx::Class::Schema::Loader::DBI::ODBC::Microsoft_SQL_Server>,
fe67d343 496L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>,
497L<DBIx::Class::Schema::Loader::DBI>
498
b87ab391 499=head1 AUTHORS
fe67d343 500
b87ab391 501See L<DBIx::Class::Schema::Loader/AUTHORS>.
fe67d343 502
be80bba7 503=head1 LICENSE
0852b7b8 504
be80bba7 505This library is free software; you can redistribute it and/or modify it under
506the same terms as Perl itself.
0852b7b8 507
fe67d343 508=cut
509
5101;
bfb43060 511# vim:et sts=4 sw=4 tw=0: