d800fe0271f31e887a6718f515ca255210faa11f
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / DBI / Sybase.pm
1 package SQL::Translator::Parser::DBI::Sybase;
2
3 # -------------------------------------------------------------------
4 # Copyright (C) 2002-2009 SQLFairy Authors
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License as
8 # published by the Free Software Foundation; version 2.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 # 02111-1307  USA
19 # -------------------------------------------------------------------
20
21 =head1 NAME
22
23 SQL::Translator::Parser::DBI::Sybase - parser for DBD::Sybase
24
25 =head1 SYNOPSIS
26
27 See SQL::Translator::Parser::DBI.
28
29 =head1 DESCRIPTION
30
31 Uses DBI Catalog Methods.
32
33 =cut
34
35 use strict;
36 use DBI;
37 use SQL::Translator::Schema;
38 use Data::Dumper;
39
40 use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
41 $VERSION = '1.59';
42 $DEBUG   = 0 unless defined $DEBUG;
43
44 no strict 'refs';
45
46 # -------------------------------------------------------------------
47 sub parse {
48     my ( $tr, $dbh ) = @_;
49
50     if ($dbh->{FetchHashKeyName} ne 'NAME_uc') {
51         warn "setting dbh attribute {FetchHashKeyName} to NAME_uc";
52         $dbh->{FetchHashKeyName} = 'NAME_uc';
53     }
54
55     if ($dbh->{ChopBlanks} != 1) {
56         warn "setting dbh attribute {ChopBlanks} to 1";
57         $dbh->{ChopBlanks} = 1;
58     }
59
60     my $schema = $tr->schema;
61
62     my ($sth, @tables, $columns);
63     my $stuff;
64
65     ### Columns
66
67     # it is much quicker to slurp back everything all at once rather
68     # than make repeated calls
69
70     $sth = $dbh->column_info(undef, undef, undef, undef);
71
72
73     foreach my $c (@{$sth->fetchall_arrayref({})}) {
74         $columns
75             ->{$c->{TABLE_CAT}}
76                 ->{$c->{TABLE_SCHEM}}
77                     ->{$c->{TABLE_NAME}}
78                         ->{columns}
79                             ->{$c->{COLUMN_NAME}}= $c;
80     }
81
82     ### Tables and views
83
84     # Get a list of the tables and views.
85     $sth = $dbh->table_info();
86     @tables   = @{$sth->fetchall_arrayref({})};
87
88     my $h = $dbh->selectall_arrayref(q{
89 SELECT o.name, colid,colid2,c.text
90   FROM syscomments c
91   JOIN sysobjects o
92     ON c.id = o.id
93  WHERE o.type ='V'
94 ORDER BY o.name,
95          c.colid,
96          c.colid2
97 }
98 );
99
100     # View text
101     # I had always thought there was something 'hard' about
102     # reconstructing text from syscomments ..
103     # this seems to work fine and is certainly not complicated!
104
105     foreach (@{$h}) {
106         $stuff->{view}->{$_->[0]}->{text} .= $_->[3];
107     }
108
109     #### objects with indexes.
110     map {
111         $stuff->{indexes}->{$_->[0]}++
112             if defined;
113     } @{$dbh->selectall_arrayref("SELECT DISTINCT object_name(id) AS name
114                                     FROM sysindexes
115                                    WHERE indid > 0")};
116
117     ## slurp objects
118     map {
119         $stuff->{$_->[1]}->{$_->[0]} = $_;
120     } @{$dbh->selectall_arrayref("SELECT name,type, id FROM sysobjects")};
121
122
123     ### Procedures
124
125     # This gets legitimate procedures by used the 'supported' API: sp_stored_procedures
126     map {
127         my $n = $_->{PROCEDURE_NAME};
128         $n =~ s/;\d+$//;        # Ignore versions for now
129         $_->{name} = $n;
130         $stuff->{procedures}->{$n} = $_;
131     } values %{$dbh->selectall_hashref("sp_stored_procedures", 'PROCEDURE_NAME')};
132
133
134     # And this blasts in the text of 'legit' stored procedures.  Do
135     # this rather than calling sp_helptext in a loop.
136
137     $h = $dbh->selectall_arrayref(q{
138 SELECT o.name, colid,colid2,c.text
139   FROM syscomments c
140   JOIN sysobjects o
141     ON c.id = o.id
142  WHERE o.type ='P'
143 ORDER BY o.name,
144          c.colid,
145          c.colid2
146 }
147 );
148
149     foreach (@{$h}) {
150         $stuff->{procedures}->{$_->[0]}->{text} .= $_->[3]
151             if (defined($stuff->{procedures}->{$_->[0]}));
152     }
153
154     ### Defaults
155     ### Rules
156     ### Bind Defaults
157     ### Bind Rules
158
159     ### Triggers
160     # Since the 'target' of the trigger is defined in the text, we will
161     # just create them independently for now rather than associating them
162     # with a table.
163
164     $h = $dbh->selectall_arrayref(q{
165 SELECT o.name, colid,colid2,c.text
166   FROM syscomments c
167   JOIN sysobjects o
168     ON c.id = o.id
169   JOIN sysobjects o1
170     ON (o.id = o1.instrig OR o.id = o1.deltrig or o.id = o1.updtrig)
171  WHERE o.type ='TR'
172 ORDER BY o.name,
173          c.colid,
174          c.colid2
175 }
176 );
177     foreach (@{$h}) {
178         $stuff->{triggers}->{$_->[0]}->{text} .= $_->[3];
179     }
180
181     ### References
182     ### Keys
183
184     ### Types
185     # Not sure what to do with these?
186     $stuff->{type_info_all} = $dbh->type_info_all;
187
188     ### Tables
189     # According to the DBI docs, these can be
190
191     # "TABLE"
192     # "VIEW"
193     # "SYSTEM TABLE"
194     # "GLOBAL TEMPORARY",
195     # "LOCAL TEMPORARY"
196     # "ALIAS"
197     # "SYNONYM"
198
199     foreach my $table_info (@tables) {
200         next
201             unless (defined($table_info->{TABLE_TYPE}));
202
203         if ($table_info->{TABLE_TYPE} =~ /TABLE/) {
204             my $table = $schema->add_table(
205                                            name =>
206 $table_info->{TABLE_NAME},
207                                            type =>
208 $table_info->{TABLE_TYPE},
209                                           ) || die $schema->error;
210
211             # find the associated columns
212
213             my $cols =
214                 $columns->{$table_info->{TABLE_QUALIFIER}}
215                     ->{$table_info->{TABLE_OWNER}}
216                         ->{$table_info->{TABLE_NAME}}
217                             ->{columns};
218
219             foreach my $c (values %{$cols}) {
220                 my $f = $table->add_field(
221                                           name        => $c->{COLUMN_NAME},
222                                           data_type   => $c->{TYPE_NAME},
223                                           order       => $c->{ORDINAL_POSITION},
224                                           size        => $c->{COLUMN_SIZE},
225                                          ) || die $table->error;
226
227                 $f->is_nullable(1)
228                     if ($c->{NULLABLE} == 1);
229             }
230
231             # add in primary key
232             my $h = $dbh->selectall_hashref("sp_pkeys
233 $table_info->{TABLE_NAME}", 'COLUMN_NAME');
234             if (scalar keys %{$h} > 1) {
235                 my @c = map {
236                     $_->{COLUMN_NAME}
237                 } sort {
238                     $a->{KEY_SEQ} <=> $b->{KEY_SEQ}
239                 } values %{$h};
240
241                 $table->primary_key(@c)
242                     if (scalar @c);
243             }
244
245             # add in any indexes ... how do we tell if the index has
246             # already been created as part of a primary key or other
247             # constraint?
248
249             if (defined($stuff->{indexes}->{$table_info->{TABLE_NAME}})){
250                 my $h = $dbh->selectall_hashref("sp_helpindex
251 $table_info->{TABLE_NAME}", 'INDEX_NAME');
252                 foreach (values %{$h}) {
253                     my $fields = $_->{'INDEX_KEYS'};
254                     $fields =~ s/\s*//g;
255                     my $i = $table->add_index(
256                                               name   =>
257 $_->{INDEX_NAME},
258                                               fields => $fields,
259                                              );
260                     if ($_->{'INDEX_DESCRIPTION'} =~ /unique/i) {
261                         $i->type('unique');
262
263                         # we could make this a primary key if there
264                         # isn't already one defined and if there
265                         # aren't any nullable columns in thisindex.
266
267                         if (!defined($table->primary_key())) {
268                             $table->primary_key($fields)
269                                 unless grep {
270                                     $table->get_field($_)->is_nullable()
271                                 } split(/,\s*/, $fields);
272                         }
273                     }
274                 }
275             }
276         } elsif ($table_info->{TABLE_TYPE} eq 'VIEW') {
277             my $view =  $schema->add_view(
278                                           name =>
279 $table_info->{TABLE_NAME},
280                                           );
281
282
283             my $cols =
284                 $columns->{$table_info->{TABLE_QUALIFIER}}
285                     ->{$table_info->{TABLE_OWNER}}
286                         ->{$table_info->{TABLE_NAME}}
287                             ->{columns};
288
289             $view->fields(map {
290                 $_->{COLUMN_NAME}
291             } sort {
292                 $a->{ORDINAL_POSITION} <=> $b->{ORDINAL_POSITION}
293                 } values %{$cols}
294                          );
295
296             $view->sql($stuff->{view}->{$table_info->{TABLE_NAME}}->{text})
297                 if (defined($stuff->{view}->{$table_info->{TABLE_NAME}}->{text}));
298         }
299     }
300
301     foreach my $p (values %{$stuff->{procedures}}) {
302         my $proc = $schema->add_procedure(
303                                name      => $p->{name},
304                                owner     => $p->{PROCEDURE_OWNER},
305                                comments  => $p->{REMARKS},
306                                sql       => $p->{text},
307                                );
308
309     }
310
311     ### Permissions
312     ### Groups
313     ### Users
314     ### Aliases
315     ### Logins
316     return 1;
317 }
318
319 1;
320
321 # -------------------------------------------------------------------
322
323 =pod
324
325 =head1 AUTHOR
326
327 Paul Harrington E<lt>harringp@deshaw.comE<gt>.
328
329 =head1 SEE ALSO
330
331 DBI, DBD::Sybase, SQL::Translator::Schema.
332
333 =cut