take out duplicate docs
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / DBI / SQLServer.pm
CommitLineData
4eb28bc3 1package SQL::Translator::Parser::DBI::SQLServer;
2
44659089 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
4eb28bc3 21=head1 NAME
22
23SQL::Translator::Parser::DBI::SQLServer - parser for SQL Server through DBD::ODBC
24
25=head1 SYNOPSIS
26
27See SQL::Translator::Parser::DBI.
28
29=head1 DESCRIPTION
30
31Uses DBI Catalog Methods.
32
33=cut
34
35use strict;
36use DBI;
37use SQL::Translator::Schema;
38use Data::Dumper;
39
da06ac74 40use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
11ad2df9 41$VERSION = '1.59';
4eb28bc3 42$DEBUG = 0 unless defined $DEBUG;
43
44no strict 'refs';
45
4eb28bc3 46sub parse {
47 my ( $tr, $dbh ) = @_;
48
49 if ($dbh->{FetchHashKeyName} ne 'NAME_uc') {
50 warn "setting dbh attribute {FetchHashKeyName} to NAME_uc";
51 $dbh->{FetchHashKeyName} = 'NAME_uc';
52 }
53
54 if ($dbh->{ChopBlanks} != 1) {
55 warn "setting dbh attribute {ChopBlanks} to 1";
56 $dbh->{ChopBlanks} = 1;
57 }
58
59 my $schema = $tr->schema;
60
61 my ($sth, @tables, $columns);
62 my $stuff;
63
64 ### Columns
65
66 # it is much quicker to slurp back everything all at once rather
67 # than make repeated calls
68
69 $sth = $dbh->column_info(undef, undef, undef, undef);
70
71
72 foreach my $c (@{$sth->fetchall_arrayref({})}) {
73 $columns
74 ->{$c->{TABLE_CAT}}
75 ->{$c->{TABLE_SCHEM}}
76 ->{$c->{TABLE_NAME}}
77 ->{columns}
78 ->{$c->{COLUMN_NAME}}= $c;
79 }
80
81 ### Tables and views
82
83 # Get a list of the tables and views.
84 $sth = $dbh->table_info();
85 @tables = @{$sth->fetchall_arrayref({})};
86
87 my $h = $dbh->selectall_arrayref(q{
88SELECT o.name, colid,c.text
89 FROM syscomments c
90 JOIN sysobjects o
91 ON c.id = o.id
92 WHERE o.type ='V'
93ORDER BY o.name,
94 c.colid
95}
96);
97
98 # View text
99 # I had always thought there was something 'hard' about
100 # reconstructing text from syscomments ..
101 # this seems to work fine and is certainly not complicated!
102
103 foreach (@{$h}) {
104 $stuff->{view}->{$_->[0]}->{text} .= $_->[2];
105 }
106
107 #### objects with indexes.
108 map {
109 $stuff->{indexes}->{$_->[0]}++
110 if defined;
111 } @{$dbh->selectall_arrayref("SELECT DISTINCT object_name(id)
112 FROM sysindexes
d4f02954 113 WHERE indid > 0 and indid < 255 and
4eb28bc3 114 name not like '_WA_Sys%'")};
115
116 ## slurp objects
117 map {
118 $stuff->{$_->[1]}->{$_->[0]} = $_;
119 } @{$dbh->selectall_arrayref("SELECT name,type, id FROM sysobjects")};
120
121
122 ### Procedures
123
124 # This gets legitimate procedures by used the 'supported' API: sp_stored_procedures
125 map {
126 my $n = $_->{PROCEDURE_NAME};
127 $n =~ s/;\d+$//; # Ignore versions for now
128 $_->{name} = $n;
129 $stuff->{procedures}->{$n} = $_;
130 } values %{$dbh->selectall_hashref("sp_stored_procedures", 'PROCEDURE_NAME')};
131
132
133 # And this blasts in the text of 'legit' stored procedures. Do
134 # this rather than calling sp_helptext in a loop.
135
136 $h = $dbh->selectall_arrayref(q{
137SELECT o.name, colid,c.text
138 FROM syscomments c
139 JOIN sysobjects o
140 ON c.id = o.id
8b3a5e87 141 WHERE o.type in ('P', 'FN', 'TF', 'IF')
4eb28bc3 142}
143);
144
145 foreach (@{$h}) {
146 $stuff->{procedures}->{$_->[0]}->{text} .= $_->[2]
147 if (defined($stuff->{procedures}->{$_->[0]}));
148 }
149
150 ### Defaults
151 ### Rules
152 ### Bind Defaults
153 ### Bind Rules
154
155 ### Triggers
156 # Since the 'target' of the trigger is defined in the text, we will
157 # just create them independently for now rather than associating them
158 # with a table.
159
160 $h = $dbh->selectall_arrayref(q{
161SELECT o.name, colid,c.text
162 FROM syscomments c
163 JOIN sysobjects o
164 ON c.id = o.id
165 JOIN sysobjects o1
166 ON (o.id = o1.instrig OR o.id = o1.deltrig or o.id = o1.updtrig)
167 WHERE o.type ='TR'
168ORDER BY o.name,
169 c.colid
170}
171);
172 foreach (@{$h}) {
173 $stuff->{triggers}->{$_->[0]}->{text} .= $_->[2];
174 }
175
176 ### References
177 ### Keys
178
179 ### Types
180 # Not sure what to do with these?
181 $stuff->{type_info_all} = $dbh->type_info_all;
182
183 ### Tables
184 # According to the DBI docs, these can be
185
186 # "TABLE"
187 # "VIEW"
188 # "SYSTEM TABLE"
189 # "GLOBAL TEMPORARY",
190 # "LOCAL TEMPORARY"
191 # "ALIAS"
192 # "SYNONYM"
193
194 foreach my $table_info (@tables) {
195 next
196 unless (defined($table_info->{TABLE_TYPE}));
197
198 if ($table_info->{TABLE_TYPE} eq "TABLE") {
199 my $table = $schema->add_table(
200 name =>
201$table_info->{TABLE_NAME},
202 type =>
203$table_info->{TABLE_TYPE},
204 ) || die $schema->error;
205
206 # find the associated columns
207
208 my $cols =
209 $columns->{$table_info->{TABLE_CAT}}
210 ->{$table_info->{TABLE_SCHEM}}
211 ->{$table_info->{TABLE_NAME}}
212 ->{columns};
213
214 foreach my $c (values %{$cols}) {
ea93df61 215 my $is_auto_increment = $c->{TYPE_NAME} =~ s#(\(\))? identity##i;
4eb28bc3 216 my $f = $table->add_field(
217 name => $c->{COLUMN_NAME},
218 data_type => $c->{TYPE_NAME},
219 order => $c->{ORDINAL_POSITION},
220 size => $c->{COLUMN_SIZE},
221 ) || die $table->error;
222 $f->is_nullable($c->{NULLABLE} == 1);
223 $f->is_auto_increment($is_auto_increment);
4e0de84c 224 if ( defined $c->{COLUMN_DEF}) {
ea93df61 225 $c->{COLUMN_DEF} =~ s#\('?(.*?)'?\)#$1#;
226 $f->default_value($c->{COLUMN_DEF});
4e0de84c 227 }
4eb28bc3 228 }
229
230 # add in primary key
231 my $h = $dbh->selectall_hashref("sp_pkeys
232$table_info->{TABLE_NAME}", 'COLUMN_NAME');
233 if (scalar keys %{$h} >= 1) {
234 my @c = map {
235 $_->{COLUMN_NAME}
236 } sort {
237 $a->{KEY_SEQ} <=> $b->{KEY_SEQ}
238 } values %{$h};
239
240 $table->primary_key(@c)
241 if (scalar @c);
242 }
243
244 # add in foreign keys
11b7c9a4 245 $h = $dbh->selectall_hashref("sp_fkeys NULL,
4eb28bc3 246\@fktable_name = '$table_info->{TABLE_NAME}'", 'FK_NAME');
ea93df61 247 foreach my $fk ( values %{$h} ) {
248 my $constraint = $table->add_constraint( name => $fk->{FK_NAME},
249 fields => [$fk->{FKCOLUMN_NAME}],
250 );
251 $constraint->type("FOREIGN_KEY");
252 $constraint->on_delete(
253 $fk->{DELETE_RULE} == 0 ? "CASCADE" :
254 $fk->{DELETE_RULE} == 1 ? "NO ACTION" : "SET_NULL"
255 );
256 $constraint->on_update(
257 $fk->{UPDATE_RULE} == 0 ? "CASCADE" :
258 $fk->{UPDATE_RULE} == 1 ? "NO ACTION" : "SET_NULL"
259 );
260 $constraint->reference_table($fk->{PKTABLE_NAME});
261 }
4eb28bc3 262
263 # add in any indexes ... how do we tell if the index has
264 # already been created as part of a primary key or other
265 # constraint?
266
267 if (defined($stuff->{indexes}->{$table_info->{TABLE_NAME}})){
268 my $h = $dbh->selectall_hashref("sp_helpindex
269$table_info->{TABLE_NAME}", 'INDEX_NAME');
270 foreach (values %{$h}) {
271 my $fields = $_->{'INDEX_KEYS'};
272 $fields =~ s/\s*//g;
273 my $i = $table->add_index(
274 name =>
275$_->{INDEX_NAME},
276 fields => $fields,
277 );
278 if ($_->{'INDEX_DESCRIPTION'} =~ /unique/i) {
279 $i->type('unique');
280
281 # we could make this a primary key if there
282 # isn't already one defined and if there
283 # aren't any nullable columns in thisindex.
284
285 if (!defined($table->primary_key())) {
286 $table->primary_key($fields)
287 unless grep {
288 $table->get_field($_)->is_nullable()
289 } split(/,\s*/, $fields);
290 }
291 }
292 }
293 }
294 } elsif ($table_info->{TABLE_TYPE} eq 'VIEW') {
ea93df61 295 next if $table_info->{TABLE_NAME} eq 'sysconstraints'
296 || $table_info->{TABLE_NAME} eq 'syssegments';
297 next if !$stuff->{view}->{$table_info->{TABLE_NAME}}->{text};
4eb28bc3 298 my $view = $schema->add_view(
299 name =>
300$table_info->{TABLE_NAME},
301 );
302
303
304 my $cols =
305 $columns->{$table_info->{TABLE_CAT}}
306 ->{$table_info->{TABLE_SCHEM}}
307 ->{$table_info->{TABLE_NAME}}
308 ->{columns};
309
310 $view->fields(map {
311 $_->{COLUMN_NAME}
312 } sort {
313 $a->{ORDINAL_POSITION} <=> $b->{ORDINAL_POSITION}
314 } values %{$cols}
315 );
316
317 $view->sql($stuff->{view}->{$table_info->{TABLE_NAME}}->{text})
318 if (defined($stuff->{view}->{$table_info->{TABLE_NAME}}->{text}));
319 }
320 }
321
322 foreach my $p (values %{$stuff->{procedures}}) {
ea93df61 323 next if !$p->{text};
4eb28bc3 324 my $proc = $schema->add_procedure(
325 name => $p->{name},
326 owner => $p->{PROCEDURE_OWNER},
327 comments => $p->{REMARKS},
328 sql => $p->{text},
329 );
330
331 }
332
333 ### Permissions
334 ### Groups
335 ### Users
336 ### Aliases
337 ### Logins
338 return 1;
339}
340
3411;
342
4eb28bc3 343=pod
344
345=head1 AUTHOR
346
347Chris Hilton E<lt>chris@dctank.comE<gt> - Bulk of code from
348DBI-Sybase parser, I just tweaked it for SQLServer. Thanks.
349
350=head1 SEE ALSO
351
352DBI, DBD::ODBC, SQL::Translator::Schema.
353
354=cut