Cosmetic change in POD.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Sybase.pm
CommitLineData
d9b22bfe 1package SQL::Translator::Producer::Sybase;
2
3# -------------------------------------------------------------------
4# $Id: Sybase.pm,v 1.1 2003-05-12 14:29:51 angiuoli Exp $
5# -------------------------------------------------------------------
6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
9#
10# This program is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public License as
12# published by the Free Software Foundation; version 2.
13#
14# This program is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17# General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22# 02111-1307 USA
23# -------------------------------------------------------------------
24
25=head1 NAME
26
27SQL::Translator::Producer::Sybase - Sybase producer for SQL::Translator
28
29=cut
30
31use strict;
32use vars qw[ $DEBUG $WARN $VERSION ];
33$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
34$DEBUG = 1 unless defined $DEBUG;
35
36use Data::Dumper;
37
38my %translate = (
39 #
40 # Sybase types
41 #
42 integer => 'numeric',
43 money => 'money',
44 varchar => 'varchar',
45 timestamp => 'datetime',
46 text => 'varchar',
47 real => 'double precision',
48 comment => 'text',
49 bit => 'bit',
50 tinyint => 'smallint',
51 float => 'double precision',
52 serial => 'numeric',
53 boolean => 'varchar',
54 char => 'char'
55
56);
57
58my %reserved = map { $_, 1 } qw[
59 ALL ANALYSE ANALYZE AND ANY AS ASC
60 BETWEEN BINARY BOTH
61 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
62 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
63 DEFAULT DEFERRABLE DESC DISTINCT DO
64 ELSE END EXCEPT
65 FALSE FOR FOREIGN FREEZE FROM FULL
66 GROUP HAVING
67 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
68 JOIN LEADING LEFT LIKE LIMIT
69 NATURAL NEW NOT NOTNULL NULL
70 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
71 PRIMARY PUBLIC REFERENCES RIGHT
72 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
73 UNION UNIQUE USER USING VERBOSE WHEN WHERE
74];
75
76my $max_id_length = 30;
77my %used_identifiers = ();
78my %global_names;
79my %unreserve;
80my %truncated;
81
82=pod
83
84=head1 Sybase Create Table Syntax
85
86 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
87 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
88 | table_constraint } [, ... ]
89 )
90 [ INHERITS ( parent_table [, ... ] ) ]
91 [ WITH OIDS | WITHOUT OIDS ]
92
93where column_constraint is:
94
95 [ CONSTRAINT constraint_name ]
96 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
97 CHECK (expression) |
98 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
99 [ ON DELETE action ] [ ON UPDATE action ] }
100 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
101
102and table_constraint is:
103
104 [ CONSTRAINT constraint_name ]
105 { UNIQUE ( column_name [, ... ] ) |
106 PRIMARY KEY ( column_name [, ... ] ) |
107 CHECK ( expression ) |
108 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
109 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
110 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
111
112=head1 Create Index Syntax
113
114 CREATE [ UNIQUE ] INDEX index_name ON table
115 [ USING acc_method ] ( column [ ops_name ] [, ...] )
116 [ WHERE predicate ]
117 CREATE [ UNIQUE ] INDEX index_name ON table
118 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
119 [ WHERE predicate ]
120
121=cut
122
123# -------------------------------------------------------------------
124sub produce {
125 my ( $translator, $data ) = @_;
126 $DEBUG = $translator->debug;
127 $WARN = $translator->show_warnings;
128 my $no_comments = $translator->no_comments;
129 my $add_drop_table = $translator->add_drop_table;
130
131 my $output;
132 unless ( $no_comments ) {
133 $output .= sprintf
134 "--\n-- Created by %s\n-- Created on %s\n--\n\n",
135 __PACKAGE__, scalar localtime;
136 }
137
138 for my $table (
139 map { $_->[1] }
140 sort { $a->[0] <=> $b->[0] }
141 map { [ $_->{'order'}, $_ ] }
142 values %$data
143 ) {
144 my $table_name = $table->{'table_name'};
145 $table_name = mk_name( $table_name, '', undef, 1 );
146 my $table_name_ur = unreserve($table_name);
147
148 my ( @comments, @field_decs, @sequence_decs, @constraints );
149
150 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
151
152 #
153 # Fields
154 #
155 my %field_name_scope;
156 for my $field (
157 map { $_->[1] }
158 sort { $a->[0] <=> $b->[0] }
159 map { [ $_->{'order'}, $_ ] }
160 values %{ $table->{'fields'} }
161 ) {
162 my $field_name = mk_name(
163 $field->{'name'}, '', \%field_name_scope, undef,1
164 );
165 my $field_name_ur = unreserve( $field_name, $table_name );
166 my $field_str = qq["$field_name_ur"];
167 $field_str =~ s/\"//g;
168 if ($field_str =~ /identity/){
169 $field_str =~ s/identity/pidentity/;
170 }
171
172 #
173 # Datatype
174 #
175 my $data_type = lc $field->{'data_type'};
176 my $orig_data_type = $data_type;
177 my $list = $field->{'list'} || [];
178 my $commalist = join ",", @$list;
179 my $seq_name;
180
181 if ( $data_type eq 'enum' ) {
182 my $len = 0;
183 $len = ($len < length($_)) ? length($_) : $len for (@$list);
184 my $check_name = mk_name( $table_name.'_'.$field_name, 'chk' ,undef,1);
185 push @constraints,
186 "CONSTRAINT $check_name CHECK ($field_name IN ($commalist))";
187 $field_str .= " character varying($len)";
188 }
189 elsif ( $data_type eq 'set' ) {
190 # XXX add a CHECK constraint maybe
191 # (trickier and slower, than enum :)
192 my $len = length $commalist;
193 $field_str .= " character varying($len) /* set $commalist */";
194 }
195 elsif ( $field->{'is_auto_inc'} ) {
196 $field_str .= ' IDENTITY';
197 }
198 else {
199 $data_type = defined $translate{ $data_type } ?
200 $translate{ $data_type } :
201 die "Unknown datatype: $data_type\n";
202 $field_str .= ' '.$data_type;
203 if ( $data_type =~ /(char|varbit|decimal)/i ) {
204 $field_str .= '('.join(',', @{ $field->{'size'} }).')'
205 if @{ $field->{'size'} || [] };
206 }
207 elsif( $data_type =~ /numeric/){
208 $field_str .= '(9,0)';
209 }
210 if( $orig_data_type eq 'text'){
211 #interpret text fields as long varchars
212 $field_str .= '(255)';
213 }
214 elsif($data_type eq "varchar" && $orig_data_type eq "boolean"){
215 $field_str .= '(6)';
216 }
217 elsif($data_type eq "varchar" && (!$field->{'size'})){
218 $field_str .= '(255)';
219 }
220 }
221
222
223 #
224 # Default value
225 #
226 if ( defined $field->{'default'} ) {
227 $field_str .= sprintf( ' DEFAULT %s',
228 ( $field->{'is_auto_inc'} && $seq_name )
229 ? qq[nextval('"$seq_name"'::text)] :
230 ( $field->{'default'} =~ m/null/i )
231 ? 'NULL' :
232 "'".$field->{'default'}."'"
233 );
234 }
235
236 #
237 # Not null constraint
238 #
239 unless ( $field->{'null'} ) {
240 my $constraint_name = mk_name($field_name_ur, 'nn',undef,1);
241# $field_str .= ' CONSTRAINT '.$constraint_name.' NOT NULL';
242 $field_str .= ' NOT NULL';
243 }
244 else {
245 $field_str .= ' NULL' if($data_type ne "bit");
246 }
247
248 push @field_decs, $field_str;
249 }
250
251 #
252 # Constraint Declarations
253 #
254 my @constraint_decs = ();
255 my $idx_name_default;
256 for my $constraint ( @{ $table->{'constraints'} } ) {
257 my $constraint_name = $constraint->{'name'} || '';
258 my $constraint_type = $constraint->{'type'} || 'normal';
259 my @fields = map { unreserve( $_, $table_name ) }
260 @{ $constraint->{'fields'} };
261 next unless @fields;
262
263 if ( $constraint_type eq 'primary_key' ) {
264 $constraint_name = mk_name( $table_name, 'pk',undef,1 );
265 push @constraints, 'CONSTRAINT '.$constraint_name.' PRIMARY KEY '.
266 '(' . join( ', ', @fields ) . ')';
267 }
268 if ( $constraint_type eq 'foreign_key' ) {
269 $constraint_name = mk_name( $table_name, 'fk',undef,1 );
270 push @constraints, 'CONSTRAINT '.$constraint_name.' FOREIGN KEY '.
271 '(' . join( ', ', @fields ) . ') '.
272 "REFERENCES $constraint->{'reference_table'}($constraint->{'reference_fields'}[0])";
273 }
274 elsif ( $constraint_type eq 'unique' ) {
275 $constraint_name = mk_name(
276 $table_name, $constraint_name || ++$idx_name_default,undef, 1
277 );
278 push @constraints, 'CONSTRAINT ' . $constraint_name . ' UNIQUE ' .
279 '(' . join( ', ', @fields ) . ')';
280 }
281 elsif ( $constraint_type eq 'normal' ) {
282 $constraint_name = mk_name(
283 $table_name, $constraint_name || ++$idx_name_default, undef, 1
284 );
285 push @constraint_decs,
286 qq[CREATE CONSTRAINT "$constraint_name" on $table_name_ur (].
287 join( ', ', @fields ).
288 ');';
289 }
290 else {
291 warn "Unknown constraint type ($constraint_type) on table $table_name.\n"
292 if $WARN;
293 }
294 }
295
296 my $create_statement;
297 $create_statement = qq[DROP TABLE $table_name_ur;\n]
298 if $add_drop_table;
299 $create_statement .= qq[CREATE TABLE $table_name_ur (\n].
300 join( ",\n", map { " $_" } @field_decs, @constraints ).
301 "\n);"
302 ;
303
304 $output .= join( "\n\n",
305 @comments,
306 @sequence_decs,
307 $create_statement,
308 @constraint_decs,
309 ''
310 );
311 }
312#
313 # Index Declarations
314 #
315 for my $table (
316 map { $_->[1] }
317 sort { $a->[0] <=> $b->[0] }
318 map { [ $_->{'order'}, $_ ] }
319 values %$data
320 ) {
321 my $table_name = $table->{'table_name'};
322 $table_name = mk_name( $table_name, '', undef, 1 );
323 my $table_name_ur = unreserve($table_name);
324
325 my @index_decs = ();
326 for my $index ( @{ $table->{'indices'} } ) {
327 my $unique = ($index->{'name'} eq 'unique') ? 'unique' : '';
328 $output .= "CREATE $unique INDEX $index->{'name'} ON $table->{'table_name'} (".join(',',@{$index->{'fields'}}).");\n";
329 }
330 }
331 if ( $WARN ) {
332 if ( %truncated ) {
333 warn "Truncated " . keys( %truncated ) . " names:\n";
334 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
335 }
336
337 if ( %unreserve ) {
338 warn "Encounted " . keys( %unreserve ) .
339 " unsafe names in schema (reserved or invalid):\n";
340 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
341 }
342 }
343
344 return $output;
345}
346
347# -------------------------------------------------------------------
348sub mk_name {
349 my ($basename, $type, $scope, $critical) = @_;
350 my $basename_orig = $basename;
351 my $max_name = $type
352 ? $max_id_length - (length($type) + 1)
353 : $max_id_length;
354 $basename = substr( $basename, 0, $max_name )
355 if length( $basename ) > $max_name;
356 my $name = $type ? "${type}_$basename" : $basename;
357 if ( $basename ne $basename_orig and $critical ) {
358 my $show_type = $type ? "+'$type'" : "";
359 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
360 "character limit to make '$name'\n" if $WARN;
361 $truncated{ $basename_orig } = $name;
362 }
363
364 $scope ||= \%global_names;
365 if ( my $prev = $scope->{ $name } ) {
366 my $name_orig = $name;
367 $name .= sprintf( "%02d", ++$prev );
368 substr($name, $max_id_length - 3) = "00"
369 if length( $name ) > $max_id_length;
370
371 warn "The name '$name_orig' has been changed to ",
372 "'$name' to make it unique.\n" if $WARN;
373
374 $scope->{ $name_orig }++;
375 }
376 $name = substr( $name, 0, $max_id_length )
377 if ((length( $name ) > $max_id_length) && $critical);
378 $scope->{ $name }++;
379 return $name;
380}
381
382# -------------------------------------------------------------------
383sub unreserve {
384 my ( $name, $schema_obj_name ) = @_;
385 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
386
387 # also trap fields that don't begin with a letter
388 return $_[0] if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
389
390 if ( $schema_obj_name ) {
391 ++$unreserve{"$schema_obj_name.$name"};
392 }
393 else {
394 ++$unreserve{"$name (table name)"};
395 }
396
397 my $unreserve = sprintf '%s_', $name;
398 return $unreserve.$suffix;
399}
400
4011;
402
403# -------------------------------------------------------------------
404# Life is full of misery, loneliness, and suffering --
405# and it's all over much too soon.
406# Woody Allen
407# -------------------------------------------------------------------
408
409=pod
410
411=head1 AUTHOR
412
413Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
414
415=cut