Release 0.07047
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / DBI / Sybase.pm
CommitLineData
fe67d343 1package DBIx::Class::Schema::Loader::DBI::Sybase;
2
3use strict;
4use warnings;
de82711a 5use base 'DBIx::Class::Schema::Loader::DBI::Sybase::Common';
942bd5e0 6use mro 'c3';
ecf22f0a 7use List::Util 'any';
c4a69b87 8use namespace::clean;
9
10use DBIx::Class::Schema::Loader::Table::Sybase ();
fe67d343 11
306bf770 12our $VERSION = '0.07047';
fe67d343 13
14=head1 NAME
15
5163dc4a 16DBIx::Class::Schema::Loader::DBI::Sybase - DBIx::Class::Schema::Loader::DBI
17Sybase ASE Implementation.
fe67d343 18
19=head1 DESCRIPTION
20
5163dc4a 21See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base>.
fe67d343 22
c4a69b87 23This class reblesses into the L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server> class for connections to MSSQL.
bc1cb85e 24
c4a69b87 25=cut
565335e6 26
fe67d343 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) {
b1e43108 33 $DBMS_VERSION =~ s/\s/_/g;
34 my $subclass = "DBIx::Class::Schema::Loader::DBI::Sybase::$DBMS_VERSION";
65f74457 35 if ($self->load_optional_class($subclass) && !$self->isa($subclass)) {
36 bless $self, $subclass;
37 $self->_rebless;
494e0205 38 }
fe67d343 39 }
40}
41
c4a69b87 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
8f65b7e5 142sub _tables_list {
5784b2b9 143 my ($self) = @_;
8f65b7e5 144
c4a69b87 145 my @tables;
8f65b7e5 146
c4a69b87 147 while (my ($db, $owners) = each %{ $self->db_schema }) {
148 foreach my $owner (@$owners) {
149 my ($uid) = $self->_uid($db, $owner);
8f65b7e5 150
c4a69b87 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 }
8f65b7e5 170
5784b2b9 171 return $self->_filter_tables(\@tables);
8f65b7e5 172}
173
c4a69b87 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
fe67d343 186sub _table_columns {
187 my ($self, $table) = @_;
188
c4a69b87 189 my $db = $table->database;
190 my $owner = $table->schema;
191
192 my $columns = $self->dbh->selectcol_arrayref(<<"EOF");
9a55cbd2 193SELECT c.name
c4a69b87 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
fe67d343 202
203 return $columns;
204}
205
206sub _table_pk_info {
207 my ($self, $table) = @_;
208
c4a69b87 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");
494e0205 218sp_pkeys @{[ $self->dbh->quote($table->name) ]},
c4a69b87 219 @{[ $self->dbh->quote($table->schema) ]},
220 @{[ $self->dbh->quote($db) ]}
221EOF
fe67d343 222 $sth->execute;
223
224 my @keydata;
225
226 while (my $row = $sth->fetchrow_hashref) {
c9373b79 227 push @keydata, $row->{column_name};
fe67d343 228 }
229
c4a69b87 230 $self->dbh->do("USE [$current_db]");
231
fe67d343 232 return \@keydata;
233}
234
235sub _table_fk_info {
236 my ($self, $table) = @_;
237
c4a69b87 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
fe67d343 257 $sth->execute;
258
c4a69b87 259 my @rels;
0852b7b8 260
c4a69b87 261 REL: while (my @rel = $sth->fetchrow_array) {
262 my ($remote_tab_id, $remote_db, $key_cnt) = splice @rel, 0, 3;
dc379dc6 263
c4a69b87 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
dc379dc6 272
c4a69b87 273 next REL
274 unless any { $_ eq $remote_tab_owner }
275 @{ $self->db_schema->{$remote_db} || [] };
dc379dc6 276
c4a69b87 277 my @local_col_ids = splice @rel, 0, 16;
278 my @remote_col_ids = splice @rel, 0, 16;
0852b7b8 279
c4a69b87 280 @local_col_ids = splice @local_col_ids, 0, $key_cnt;
281 @remote_col_ids = splice @remote_col_ids, 0, $key_cnt;
0852b7b8 282
c4a69b87 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 );
0852b7b8 289
c4a69b87 290 my $all_local_cols = $self->_table_columns($table);
291 my $all_remote_cols = $self->_table_columns($remote_table);
0852b7b8 292
c4a69b87 293 my @local_cols = map $all_local_cols->[$_-1], @local_col_ids;
294 my @remote_cols = map $all_remote_cols->[$_-1], @remote_col_ids;
dc379dc6 295
c4a69b87 296 next REL if (any { not defined $_ } @local_cols)
297 || (any { not defined $_ } @remote_cols);
dc379dc6 298
299 push @rels, {
300 local_columns => \@local_cols,
dc379dc6 301 remote_table => $remote_table,
c4a69b87 302 remote_columns => \@remote_cols,
dc379dc6 303 };
c4a69b87 304 };
0852b7b8 305
306 return \@rels;
307}
308
fe67d343 309sub _table_uniq_info {
310 my ($self, $table) = @_;
311
c4a69b87 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;
0852b7b8 332
c4a69b87 333 my %uniqs;
fe67d343 334
c4a69b87 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;
fe67d343 346 }
347 }
348
c4a69b87 349 $self->dbh->do("USE [$current_db]");
350
6c4f5a4a 351 return [ map { [ $_ => $uniqs{$_} ] } sort keys %uniqs ];
fe67d343 352}
353
db4d62ad 354sub _columns_info_for {
355 my $self = shift;
356 my ($table) = @_;
357 my $result = $self->next::method(@_);
358
c4a69b87 359 my $db = $table->database;
360 my $owner = $table->schema;
361 my $uid = $self->_uid($db, $owner);
362
363 local $self->dbh->{FetchHashKeyName} = 'NAME_lc';
364 my $sth = $self->dbh->prepare(<<"EOF");
006c8ed3 365SELECT 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
c4a69b87 366FROM [$db].dbo.syscolumns c
006c8ed3 367LEFT JOIN [$db].dbo.sysobjects o ON c.id = o.id
368LEFT JOIN [$db].dbo.systypes bt ON c.type = bt.type
369LEFT JOIN [$db].dbo.systypes ut ON c.usertype = ut.usertype
c4a69b87 370WHERE o.name = @{[ $self->dbh->quote($table) ]}
371 AND o.uid = $uid
372 AND o.type IN ('U', 'V')
373EOF
db4d62ad 374 $sth->execute;
2fb9a4b3 375 my $info = $sth->fetchall_hashref('name');
db4d62ad 376
2fb9a4b3 377 while (my ($col, $res) = each %$result) {
006c8ed3 378 $res->{data_type} = $info->{$col}{user_type} || $info->{$col}{base_type};
c4a69b87 379
006c8ed3 380 if ($info->{$col}{is_id}) {
c4a69b87 381 $res->{is_auto_increment} = 1;
382 }
e17ad40a 383 $sth->finish;
41968729 384
006c8ed3 385 # column has default value
386 if (my $default_id = $info->{$col}{dflt_id}) {
387 my $sth = $self->dbh->prepare(<<"EOF");
388SELECT cm.id, cm.text
389FROM [$db].dbo.syscomments cm
390WHERE cm.id = $default_id
391EOF
392 $sth->execute;
393
394 if (my ($d_id, $default) = $sth->fetchrow_array) {
395 my $constant_default = ($default =~ /^DEFAULT \s+ (\S.*\S)/ix)
396 ? $1
397 : $default;
398
399 $constant_default = substr($constant_default, 1, length($constant_default) - 2)
400 if ( substr($constant_default, 0, 1) =~ m{['"\[]}
401 && substr($constant_default, -1) =~ m{['"\]]});
402
403 $res->{default_value} = $constant_default;
404 }
41968729 405 }
2fb9a4b3 406
006c8ed3 407 # column is a computed value
408 if (my $comp_id = $info->{$col}{comp_id}) {
409 my $sth = $self->dbh->prepare(<<"EOF");
410SELECT cm.id, cm.text
411FROM [$db].dbo.syscomments cm
412WHERE cm.id = $comp_id
413EOF
414 $sth->execute;
415 if (my ($c_id, $comp) = $sth->fetchrow_array) {
416 my $function = ($comp =~ /^AS \s+ (\S+)/ix) ? $1 : $comp;
2fb9a4b3 417 $res->{default_value} = \$function;
0faae4b8 418
419 if ($function =~ /^getdate\b/) {
420 $res->{inflate_datetime} = 1;
421 }
5163dc4a 422
423 delete $res->{size};
424 $res->{data_type} = undef;
2fb9a4b3 425 }
2fb9a4b3 426 }
6ecee584 427
6ecee584 428 if (my $data_type = $res->{data_type}) {
5163dc4a 429 if ($data_type eq 'int') {
430 $data_type = $res->{data_type} = 'integer';
431 }
432 elsif ($data_type eq 'decimal') {
433 $data_type = $res->{data_type} = 'numeric';
434 }
006c8ed3 435 elsif ($data_type eq 'float') {
436 $data_type = $res->{data_type}
437 = ($info->{$col}{len} <= 4 ? 'real' : 'double precision');
438 }
439
440 if ($data_type eq 'timestamp') {
441 $res->{inflate_datetime} = 0;
442 }
5163dc4a 443
444 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) {
6ecee584 445 delete $res->{size};
446 }
5163dc4a 447 elsif ($data_type eq 'numeric') {
448 my ($prec, $scale) = @{$info->{$col}}{qw/prec scale/};
449
006c8ed3 450 if (!defined $prec && !defined $scale) {
451 $data_type = $res->{data_type} = 'integer';
452 delete $res->{size};
453 }
454 elsif ($prec == 18 && $scale == 0) {
5163dc4a 455 delete $res->{size};
456 }
457 else {
458 $res->{size} = [ $prec, $scale ];
459 }
6ecee584 460 }
c4a69b87 461 elsif ($data_type =~ /char/) {
462 $res->{size} = $info->{$col}{len};
463
464 if ($data_type =~ /^(?:unichar|univarchar)\z/i) {
465 $res->{size} /= 2;
466 }
e17ad40a 467 elsif ($data_type =~ /^n(?:var)?char\z/i) {
468 my ($nchar_size) = $self->dbh->selectrow_array('SELECT @@ncharsize');
469
470 $res->{size} /= $nchar_size;
471 }
6ecee584 472 }
473 }
db4d62ad 474 }
475
476 return $result;
477}
478
fe67d343 479=head1 SEE ALSO
480
5163dc4a 481L<DBIx::Class::Schema::Loader::DBI::Sybase::Common>,
fe67d343 482L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>,
483L<DBIx::Class::Schema::Loader::DBI>
484
b87ab391 485=head1 AUTHORS
fe67d343 486
b87ab391 487See L<DBIx::Class::Schema::Loader/AUTHORS>.
fe67d343 488
be80bba7 489=head1 LICENSE
0852b7b8 490
be80bba7 491This library is free software; you can redistribute it and/or modify it under
492the same terms as Perl itself.
0852b7b8 493
fe67d343 494=cut
495
4961;
db9c411a 497# vim:et sts=4 sw=4 tw=0: