1package DBIx::Class::Schema::Loader::DBI::Sybase; 2 3use strict; 4use warnings; 5use base 'DBIx::Class::Schema::Loader::DBI::Sybase::Common'; 6use mro 'c3'; 7use List::MoreUtils 'any'; 8use namespace::clean; 9 10use DBIx::Class::Schema::Loader::Table::Sybase (); 11 12our $VERSION = '0.07033'; 13 14=head1 NAME 15 16DBIx::Class::Schema::Loader::DBI::Sybase - DBIx::Class::Schema::Loader::DBI 17Sybase ASE Implementation. 18 19=head1 DESCRIPTION 20 21See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base>. 22 23This class reblesses into the L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server> class for connections to MSSQL. 24 25=cut 26 27sub _rebless { 28 my $self = shift; 29 30 my $dbh = $self->schema->storage->dbh; 31 my $DBMS_VERSION = @{$dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]; 32 if ($DBMS_VERSION =~ /^Microsoft /i) { 33 $DBMS_VERSION =~ s/\s/_/g; 34 my $subclass = "DBIx::Class::Schema::Loader::DBI::Sybase::$DBMS_VERSION"; 35 if ($self->load_optional_class($subclass) && !$self->isa($subclass)) { 36 bless $self, $subclass; 37 $self->_rebless; 38 } 39 } 40} 41 42sub _system_databases { 43 return (qw/ 44 master model sybsystemdb sybsystemprocs tempdb 45 /); 46} 47 48sub _system_tables { 49 return (qw/ 50 sysquerymetrics 51 /); 52} 53 54sub _setup { 55 my $self = shift; 56 57 $self->next::method(@_); 58 59 $self->preserve_case(1); 60 61 my ($current_db) = $self->dbh->selectrow_array('SELECT db_name()'); 62 63 if (ref $self->db_schema eq 'HASH') { 64 if (keys %{ $self->db_schema } < 2) { 65 my ($db) = keys %{ $self->db_schema }; 66 67 $db ||= $current_db; 68 69 if ($db eq '%') { 70 my $owners = $self->db_schema->{$db}; 71 72 my $db_names = $self->dbh->selectcol_arrayref(<<'EOF'); 73SELECT name 74FROM master.dbo.sysdatabases 75EOF 76 77 my @dbs; 78 79 foreach my $db_name (@$db_names) { 80 push @dbs, $db_name 81 unless any { $_ eq $db_name } $self->_system_databases; 82 } 83 84 $self->db_schema({}); 85 86 DB: foreach my $db (@dbs) { 87 if (not ((ref $owners eq 'ARRAY' && $owners->[0] eq '%') || $owners eq '%')) { 88 my @owners; 89 90 foreach my $owner (@$owners) { 91 push @owners, $owner 92 if defined $self->_uid($db, $owner); 93 } 94 95 next DB unless @owners; 96 97 $self->db_schema->{$db} = \@owners; 98 } 99 else { 100 # for post-processing below 101 $self->db_schema->{$db} = '%'; 102 } 103 } 104 105 $self->qualify_objects(1); 106 } 107 else { 108 if ($db ne $current_db) { 109 $self->dbh->do("USE [$db]"); 110 111 $self->qualify_objects(1); 112 } 113 } 114 } 115 else { 116 $self->qualify_objects(1); 117 } 118 } 119 elsif (ref $self->db_schema eq 'ARRAY' || (not defined $self->db_schema)) { 120 my $owners = $self->db_schema; 121 $owners ||= [ $self->dbh->selectrow_array('SELECT user_name()') ]; 122 123 $self->qualify_objects(1) if @$owners > 1; 124 125 $self->db_schema({ $current_db => $owners }); 126 } 127 128 foreach my $db (keys %{ $self->db_schema }) { 129 if ($self->db_schema->{$db} eq '%') { 130 my $owners = $self->dbh->selectcol_arrayref(<<"EOF"); 131SELECT name 132FROM [$db].dbo.sysusers 133WHERE uid <> gid 134EOF 135 $self->db_schema->{$db} = $owners; 136 137 $self->qualify_objects(1); 138 } 139 } 140} 141 142sub _tables_list { 143 my ($self, $opts) = @_; 144 145 my @tables; 146 147 while (my ($db, $owners) = each %{ $self->db_schema }) { 148 foreach my $owner (@$owners) { 149 my ($uid) = $self->_uid($db, $owner); 150 151 my $table_names = $self->dbh->selectcol_arrayref(<<"EOF"); 152SELECT name 153FROM [$db].dbo.sysobjects 154WHERE uid = $uid 155 AND type IN ('U', 'V') 156EOF 157 158 TABLE: foreach my $table_name (@$table_names) { 159 next TABLE if any { $_ eq $table_name } $self->_system_tables; 160 161 push @tables, DBIx::Class::Schema::Loader::Table::Sybase->new( 162 loader => $self, 163 name => $table_name, 164 database => $db, 165 schema => $owner, 166 ); 167 } 168 } 169 } 170 171 return $self->_filter_tables(\@tables, $opts); 172} 173 174sub _uid { 175 my ($self, $db, $owner) = @_; 176 177 my ($uid) = $self->dbh->selectrow_array(<<"EOF"); 178SELECT uid 179FROM [$db].dbo.sysusers 180WHERE name = @{[ $self->dbh->quote($owner) ]} 181EOF 182 183 return $uid; 184} 185 186sub _table_columns { 187 my ($self, $table) = @_; 188 189 my $db = $table->database; 190 my $owner = $table->schema; 191 192 my $columns = $self->dbh->selectcol_arrayref(<<"EOF"); 193SELECT c.name 194FROM [$db].dbo.syscolumns c 195JOIN [$db].dbo.sysobjects o 196 ON c.id = o.id 197WHERE o.name = @{[ $self->dbh->quote($table->name) ]} 198 AND o.type IN ('U', 'V') 199 AND o.uid = @{[ $self->_uid($db, $owner) ]} 200ORDER BY c.colid ASC 201EOF 202 203 return $columns; 204} 205 206sub _table_pk_info { 207 my ($self, $table) = @_; 208 209 my ($current_db) = $self->dbh->selectrow_array('SELECT db_name()'); 210 211 my $db = $table->database; 212 213 $self->dbh->do("USE [$db]"); 214 215 local $self->dbh->{FetchHashKeyName} = 'NAME_lc'; 216 217 my $sth = $self->dbh->prepare(<<"EOF"); 218sp_pkeys @{[ $self->dbh->quote($table->name) ]}, 219 @{[ $self->dbh->quote($table->schema) ]}, 220 @{[ $self->dbh->quote($db) ]} 221EOF 222 $sth->execute; 223 224 my @keydata; 225 226 while (my $row = $sth->fetchrow_hashref) { 227 push @keydata, $row->{column_name}; 228 } 229 230 $self->dbh->do("USE [$current_db]"); 231 232 return \@keydata; 233} 234 235sub _table_fk_info { 236 my ($self, $table) = @_; 237 238 my $db = $table->database; 239 my $owner = $table->schema; 240 241 my $sth = $self->dbh->prepare(<<"EOF"); 242SELECT sr.reftabid, sd2.name, sr.keycnt, 243 fokey1, fokey2, fokey3, fokey4, fokey5, fokey6, fokey7, fokey8, 244 fokey9, fokey10, fokey11, fokey12, fokey13, fokey14, fokey15, fokey16, 245 refkey1, refkey2, refkey3, refkey4, refkey5, refkey6, refkey7, refkey8, 246 refkey9, refkey10, refkey11, refkey12, refkey13, refkey14, refkey15, refkey16 247FROM [$db].dbo.sysreferences sr 248JOIN [$db].dbo.sysobjects so1 249 ON sr.tableid = so1.id 250JOIN [$db].dbo.sysusers su1 251 ON so1.uid = su1.uid 252JOIN master.dbo.sysdatabases sd2 253 ON sr.pmrydbid = sd2.dbid 254WHERE so1.name = @{[ $self->dbh->quote($table->name) ]} 255 AND su1.name = @{[ $self->dbh->quote($table->schema) ]} 256EOF 257 $sth->execute; 258 259 my @rels; 260 261 REL: while (my @rel = $sth->fetchrow_array) { 262 my ($remote_tab_id, $remote_db, $key_cnt) = splice @rel, 0, 3; 263 264 my ($remote_tab_owner, $remote_tab_name) = 265 $self->dbh->selectrow_array(<<"EOF"); 266SELECT su.name, so.name 267FROM [$remote_db].dbo.sysusers su 268JOIN [$remote_db].dbo.sysobjects so 269 ON su.uid = so.uid 270WHERE so.id = $remote_tab_id 271EOF 272 273 next REL 274 unless any { $_ eq $remote_tab_owner } 275 @{ $self->db_schema->{$remote_db} || [] }; 276 277 my @local_col_ids = splice @rel, 0, 16; 278 my @remote_col_ids = splice @rel, 0, 16; 279 280 @local_col_ids = splice @local_col_ids, 0, $key_cnt; 281 @remote_col_ids = splice @remote_col_ids, 0, $key_cnt; 282 283 my $remote_table = DBIx::Class::Schema::Loader::Table::Sybase->new( 284 loader => $self, 285 name => $remote_tab_name, 286 database => $remote_db, 287 schema => $remote_tab_owner, 288 ); 289 290 my $all_local_cols = $self->_table_columns($table); 291 my $all_remote_cols = $self->_table_columns($remote_table); 292 293 my @local_cols = map $all_local_cols->[$_-1], @local_col_ids; 294 my @remote_cols = map $all_remote_cols->[$_-1], @remote_col_ids; 295 296 next REL if (any { not defined $_ } @local_cols) 297 || (any { not defined $_ } @remote_cols); 298 299 push @rels, { 300 local_columns => \@local_cols, 301 remote_table => $remote_table, 302 remote_columns => \@remote_cols, 303 }; 304 }; 305 306 return \@rels; 307} 308 309sub _table_uniq_info { 310 my ($self, $table) = @_; 311 312 my $db = $table->database; 313 my $owner = $table->schema; 314 my $uid = $self->_uid($db, $owner); 315 316 my ($current_db) = $self->dbh->selectrow_array('SELECT db_name()'); 317 318 $self->dbh->do("USE [$db]"); 319 320 my $sth = $self->dbh->prepare(<<"EOF"); 321SELECT si.name, si.indid, si.keycnt 322FROM [$db].dbo.sysindexes si 323JOIN [$db].dbo.sysobjects so 324 ON si.id = so.id 325WHERE so.name = @{[ $self->dbh->quote($table->name) ]} 326 AND so.uid = $uid 327 AND si.indid > 0 328 AND si.status & 2048 <> 2048 329 AND si.status2 & 2 = 2 330EOF 331 $sth->execute; 332 333 my %uniqs; 334 335 while (my ($ind_name, $ind_id, $key_cnt) = $sth->fetchrow_array) { 336 COLS: foreach my $col_idx (1 .. ($key_cnt+1)) { 337 my ($next_col) = $self->dbh->selectrow_array(<<"EOF"); 338SELECT index_col( 339 @{[ $self->dbh->quote($table->name) ]}, 340 $ind_id, $col_idx, $uid 341) 342EOF 343 last COLS unless defined $next_col; 344 345 push @{ $uniqs{$ind_name} }, $next_col; 346 } 347 } 348 349 my @uniqs = map { [ $_ => $uniqs{$_} ] } keys %uniqs; 350 351 $self->dbh->do("USE [$current_db]"); 352 353 return \@uniqs; 354} 355 356sub _columns_info_for { 357 my $self = shift; 358 my ($table) = @_; 359 my $result = $self->next::method(@_); 360 361 my $db = $table->database; 362 my $owner = $table->schema; 363 my $uid = $self->_uid($db, $owner); 364 365 local $self->dbh->{FetchHashKeyName} = 'NAME_lc'; 366 my $sth = $self->dbh->prepare(<<"EOF"); 367SELECT c.name, bt.name base_type, ut.name user_type, c.prec prec, c.scale scale, c.length len, c.cdefault dflt_id, c.computedcol comp_id, (c.status & 0x80) is_id 368FROM [$db].dbo.syscolumns c 369LEFT JOIN [$db].dbo.sysobjects o ON c.id = o.id 370LEFT JOIN [$db].dbo.systypes bt ON c.type = bt.type 371LEFT JOIN [$db].dbo.systypes ut ON c.usertype = ut.usertype 372WHERE o.name = @{[ $self->dbh->quote($table) ]} 373 AND o.uid = $uid 374 AND o.type IN ('U', 'V') 375EOF 376 $sth->execute; 377 my $info = $sth->fetchall_hashref('name'); 378 379 while (my ($col, $res) = each %$result) { 380 $res->{data_type} = $info->{$col}{user_type} || $info->{$col}{base_type}; 381 382 if ($info->{$col}{is_id}) { 383 $res->{is_auto_increment} = 1; 384 } 385 $sth->finish; 386 387 # column has default value 388 if (my $default_id = $info->{$col}{dflt_id}) { 389 my $sth = $self->dbh->prepare(<<"EOF"); 390SELECT cm.id, cm.text 391FROM [$db].dbo.syscomments cm 392WHERE cm.id = $default_id 393EOF 394 $sth->execute; 395 396 if (my ($d_id, $default) = $sth->fetchrow_array) { 397 my $constant_default = ($default =~ /^DEFAULT \s+ (\S.*\S)/ix) 398 ? $1 399 : $default; 400 401 $constant_default = substr($constant_default, 1, length($constant_default) - 2) 402 if ( substr($constant_default, 0, 1) =~ m{['"\[]} 403 && substr($constant_default, -1) =~ m{['"\]]}); 404 405 $res->{default_value} = $constant_default; 406 } 407 } 408 409 # column is a computed value 410 if (my $comp_id = $info->{$col}{comp_id}) { 411 my $sth = $self->dbh->prepare(<<"EOF"); 412SELECT cm.id, cm.text 413FROM [$db].dbo.syscomments cm 414WHERE cm.id = $comp_id 415EOF 416 $sth->execute; 417 if (my ($c_id, $comp) = $sth->fetchrow_array) { 418 my $function = ($comp =~ /^AS \s+ (\S+)/ix) ? $1 : $comp; 419 $res->{default_value} = \$function; 420 421 if ($function =~ /^getdate\b/) { 422 $res->{inflate_datetime} = 1; 423 } 424 425 delete $res->{size}; 426 $res->{data_type} = undef; 427 } 428 } 429 430 if (my $data_type = $res->{data_type}) { 431 if ($data_type eq 'int') { 432 $data_type = $res->{data_type} = 'integer'; 433 } 434 elsif ($data_type eq 'decimal') { 435 $data_type = $res->{data_type} = 'numeric'; 436 } 437 elsif ($data_type eq 'float') { 438 $data_type = $res->{data_type} 439 = ($info->{$col}{len} <= 4 ? 'real' : 'double precision'); 440 } 441 442 if ($data_type eq 'timestamp') { 443 $res->{inflate_datetime} = 0; 444 } 445 446 if ($data_type =~ /^(?:text|unitext|image|bigint|integer|smallint|tinyint|real|double|double precision|float|date|time|datetime|smalldatetime|money|smallmoney|timestamp|bit)\z/i) { 447 delete $res->{size}; 448 } 449 elsif ($data_type eq 'numeric') { 450 my ($prec, $scale) = @{$info->{$col}}{qw/prec scale/}; 451 452 if (!defined $prec && !defined $scale) { 453 $data_type = $res->{data_type} = 'integer'; 454 delete $res->{size}; 455 } 456 elsif ($prec == 18 && $scale == 0) { 457 delete $res->{size}; 458 } 459 else { 460 $res->{size} = [ $prec, $scale ]; 461 } 462 } 463 elsif ($data_type =~ /char/) { 464 $res->{size} = $info->{$col}{len}; 465 466 if ($data_type =~ /^(?:unichar|univarchar)\z/i) { 467 $res->{size} /= 2; 468 } 469 elsif ($data_type =~ /^n(?:var)?char\z/i) { 470 my ($nchar_size) = $self->dbh->selectrow_array('SELECT @@ncharsize'); 471 472 $res->{size} /= $nchar_size; 473 } 474 } 475 } 476 } 477 478 return $result; 479} 480 481=head1 SEE ALSO 482 483L<DBIx::Class::Schema::Loader::DBI::Sybase::Common>, 484L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>, 485L<DBIx::Class::Schema::Loader::DBI> 486 487=head1 AUTHOR 488 489See L<DBIx::Class::Schema::Loader/AUTHOR> and L<DBIx::Class::Schema::Loader/CONTRIBUTORS>. 490 491=head1 LICENSE 492 493This library is free software; you can redistribute it and/or modify it under 494the same terms as Perl itself. 495 496=cut 497 4981; 499# vim:et sts=4 sw=4 tw=0: 500