datatype mapping; IDENTITY
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Sybase.pm
CommitLineData
d9b22bfe 1package SQL::Translator::Producer::Sybase;
2
d9b22bfe 3=head1 NAME
4
5SQL::Translator::Producer::Sybase - Sybase producer for SQL::Translator
6
75c75c55 7=head1 SYNOPSIS
8
9 use SQL::Translator;
10
11 my $t = SQL::Translator->new( parser => '...', producer => 'Sybase' );
12 $t->translate;
13
14=head1 DESCRIPTION
15
16This module will produce text output of the schema suitable for Sybase.
17
d9b22bfe 18=cut
19
20use strict;
f27f9229 21use warnings;
0c04c5a2 22our ( $DEBUG, $WARN );
da93ce68 23our $VERSION = '1.60';
d9b22bfe 24$DEBUG = 1 unless defined $DEBUG;
25
26use Data::Dumper;
54c9812d 27use SQL::Translator::Schema::Constants;
590f4d4a 28use SQL::Translator::Utils qw(debug header_comment);
d9b22bfe 29
30my %translate = (
31 #
32 # Sybase types
33 #
54c9812d 34 integer => 'numeric',
35 int => 'numeric',
36 number => 'numeric',
37 money => 'money',
38 varchar => 'varchar',
39 varchar2 => 'varchar',
40 timestamp => 'datetime',
41 text => 'varchar',
42 real => 'double precision',
43 comment => 'text',
44 bit => 'bit',
45 tinyint => 'smallint',
46 float => 'double precision',
ea93df61 47 serial => 'numeric',
54c9812d 48 boolean => 'varchar',
49 char => 'char',
50 long => 'varchar',
d9b22bfe 51);
52
53my %reserved = map { $_, 1 } qw[
ea93df61 54 ALL ANALYSE ANALYZE AND ANY AS ASC
d9b22bfe 55 BETWEEN BINARY BOTH
56 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
ea93df61 57 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
d9b22bfe 58 DEFAULT DEFERRABLE DESC DISTINCT DO
59 ELSE END EXCEPT
ea93df61 60 FALSE FOR FOREIGN FREEZE FROM FULL
61 GROUP HAVING
62 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
63 JOIN LEADING LEFT LIKE LIMIT
d9b22bfe 64 NATURAL NEW NOT NOTNULL NULL
65 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
ea93df61 66 PRIMARY PUBLIC REFERENCES RIGHT
67 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
d9b22bfe 68 UNION UNIQUE USER USING VERBOSE WHEN WHERE
69];
70
71my $max_id_length = 30;
72my %used_identifiers = ();
73my %global_names;
74my %unreserve;
75my %truncated;
76
77=pod
78
79=head1 Sybase Create Table Syntax
80
81 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
82 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
83 | table_constraint } [, ... ]
84 )
85 [ INHERITS ( parent_table [, ... ] ) ]
86 [ WITH OIDS | WITHOUT OIDS ]
87
88where column_constraint is:
89
90 [ CONSTRAINT constraint_name ]
91 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
92 CHECK (expression) |
93 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
94 [ ON DELETE action ] [ ON UPDATE action ] }
95 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
96
97and table_constraint is:
98
99 [ CONSTRAINT constraint_name ]
100 { UNIQUE ( column_name [, ... ] ) |
101 PRIMARY KEY ( column_name [, ... ] ) |
102 CHECK ( expression ) |
103 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
104 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
105 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
106
107=head1 Create Index Syntax
108
109 CREATE [ UNIQUE ] INDEX index_name ON table
110 [ USING acc_method ] ( column [ ops_name ] [, ...] )
111 [ WHERE predicate ]
112 CREATE [ UNIQUE ] INDEX index_name ON table
113 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
114 [ WHERE predicate ]
115
116=cut
117
d9b22bfe 118sub produce {
a1d94525 119 my $translator = shift;
120 $DEBUG = $translator->debug;
121 $WARN = $translator->show_warnings;
122 my $no_comments = $translator->no_comments;
123 my $add_drop_table = $translator->add_drop_table;
124 my $schema = $translator->schema;
d9b22bfe 125
126 my $output;
590f4d4a 127 $output .= header_comment unless ($no_comments);
d9b22bfe 128
54c9812d 129 for my $table ( $schema->get_tables ) {
130 my $table_name = $table->name or next;
d9b22bfe 131 $table_name = mk_name( $table_name, '', undef, 1 );
54c9812d 132 my $table_name_ur = unreserve($table_name) || '';
d9b22bfe 133
54c9812d 134 my ( @comments, @field_defs, @index_defs, @constraint_defs );
d9b22bfe 135
136 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
137
54c9812d 138 push @comments, map { "-- $_" } $table->comments;
139
d9b22bfe 140 #
141 # Fields
142 #
143 my %field_name_scope;
54c9812d 144 for my $field ( $table->get_fields ) {
d9b22bfe 145 my $field_name = mk_name(
ea93df61 146 $field->name, '', \%field_name_scope, undef,1
d9b22bfe 147 );
148 my $field_name_ur = unreserve( $field_name, $table_name );
54c9812d 149 my $field_def = qq["$field_name_ur"];
150 $field_def =~ s/\"//g;
151 if ( $field_def =~ /identity/ ){
152 $field_def =~ s/identity/pidentity/;
590f4d4a 153 }
d9b22bfe 154
155 #
156 # Datatype
157 #
54c9812d 158 my $data_type = lc $field->data_type;
590f4d4a 159 my $orig_data_type = $data_type;
54c9812d 160 my %extra = $field->extra;
161 my $list = $extra{'list'} || [];
77d74ea6 162 # \todo deal with embedded quotes
4524cf01 163 my $commalist = join( ', ', map { qq['$_'] } @$list );
d9b22bfe 164 my $seq_name;
165
bde41930 166 my $identity = '';
167
d9b22bfe 168 if ( $data_type eq 'enum' ) {
ea93df61 169 my $check_name = mk_name(
590f4d4a 170 $table_name.'_'.$field_name, 'chk' ,undef, 1
171 );
ea93df61 172 push @constraint_defs,
d9b22bfe 173 "CONSTRAINT $check_name CHECK ($field_name IN ($commalist))";
54c9812d 174 $data_type .= 'character varying';
d9b22bfe 175 }
176 elsif ( $data_type eq 'set' ) {
54c9812d 177 $data_type .= 'character varying';
d9b22bfe 178 }
d9b22bfe 179 else {
bde41930 180 if ( $field->is_auto_increment ) {
181 $identity = 'IDENTITY';
182 }
54c9812d 183 if ( defined $translate{ $data_type } ) {
184 $data_type = $translate{ $data_type };
d9b22bfe 185 }
54c9812d 186 else {
187 warn "Unknown datatype: $data_type ",
188 "($table_name.$field_name)\n" if $WARN;
590f4d4a 189 }
54c9812d 190 }
590f4d4a 191
54c9812d 192 my $size = $field->size;
193 unless ( $size ) {
194 if ( $data_type =~ /numeric/ ) {
195 $size = '9,0';
196 }
197 elsif ( $orig_data_type eq 'text' ) {
590f4d4a 198 #interpret text fields as long varchars
54c9812d 199 $size = '255';
590f4d4a 200 }
54c9812d 201 elsif (
ea93df61 202 $data_type eq 'varchar' &&
54c9812d 203 $orig_data_type eq 'boolean'
204 ) {
205 $size = '6';
590f4d4a 206 }
54c9812d 207 elsif ( $data_type eq 'varchar' ) {
208 $size = '255';
590f4d4a 209 }
d9b22bfe 210 }
211
54c9812d 212 $field_def .= " $data_type";
213 $field_def .= "($size)" if $size;
bde41930 214 $field_def .= " $identity" if $identity;
d9b22bfe 215
216 #
217 # Default value
218 #
54c9812d 219 my $default = $field->default_value;
220 if ( defined $default ) {
221 $field_def .= sprintf( ' DEFAULT %s',
222 ( $field->is_auto_increment && $seq_name )
d9b22bfe 223 ? qq[nextval('"$seq_name"'::text)] :
54c9812d 224 ( $default =~ m/null/i ) ? 'NULL' : "'$default'"
d9b22bfe 225 );
226 }
227
228 #
229 # Not null constraint
230 #
54c9812d 231 unless ( $field->is_nullable ) {
232 $field_def .= ' NOT NULL';
d9b22bfe 233 }
590f4d4a 234 else {
54c9812d 235 $field_def .= ' NULL' if $data_type ne 'bit';
590f4d4a 236 }
d9b22bfe 237
54c9812d 238 push @field_defs, $field_def;
d9b22bfe 239 }
240
241 #
242 # Constraint Declarations
243 #
244 my @constraint_decs = ();
54c9812d 245 my $c_name_default;
246 for my $constraint ( $table->get_constraints ) {
247 my $name = $constraint->name || '';
248 my $type = $constraint->type || NORMAL;
249 my @fields = map { unreserve( $_, $table_name ) }
250 $constraint->fields;
251 my @rfields = map { unreserve( $_, $table_name ) }
252 $constraint->reference_fields;
d9b22bfe 253 next unless @fields;
254
54c9812d 255 if ( $type eq PRIMARY_KEY ) {
256 $name ||= mk_name( $table_name, 'pk', undef,1 );
ea93df61 257 push @constraint_defs,
54c9812d 258 "CONSTRAINT $name PRIMARY KEY ".
d9b22bfe 259 '(' . join( ', ', @fields ) . ')';
260 }
54c9812d 261 elsif ( $type eq FOREIGN_KEY ) {
262 $name ||= mk_name( $table_name, 'fk', undef,1 );
ea93df61 263 push @constraint_defs,
54c9812d 264 "CONSTRAINT $name FOREIGN KEY".
265 ' (' . join( ', ', @fields ) . ') REFERENCES '.
266 $constraint->reference_table.
267 ' (' . join( ', ', @rfields ) . ')';
d9b22bfe 268 }
54c9812d 269 elsif ( $type eq UNIQUE ) {
ea93df61 270 $name ||= mk_name(
271 $table_name,
54c9812d 272 $name || ++$c_name_default,undef, 1
d9b22bfe 273 );
ea93df61 274 push @constraint_defs,
54c9812d 275 "CONSTRAINT $name UNIQUE " .
d9b22bfe 276 '(' . join( ', ', @fields ) . ')';
277 }
54c9812d 278 }
279
280 #
281 # Indices
282 #
283 for my $index ( $table->get_indices ) {
ea93df61 284 push @index_defs,
54c9812d 285 'CREATE INDEX ' . $index->name .
286 " ON $table_name (".
287 join( ', ', $index->fields ) . ");";
d9b22bfe 288 }
289
290 my $create_statement;
ea93df61 291 $create_statement = qq[DROP TABLE $table_name_ur;\n]
d9b22bfe 292 if $add_drop_table;
293 $create_statement .= qq[CREATE TABLE $table_name_ur (\n].
ea93df61 294 join( ",\n",
295 map { " $_" } @field_defs, @constraint_defs
54c9812d 296 ).
d9b22bfe 297 "\n);"
298 ;
299
ea93df61 300 $output .= join( "\n\n",
d9b22bfe 301 @comments,
ea93df61 302 $create_statement,
303 @index_defs,
54c9812d 304 ''
d9b22bfe 305 );
590f4d4a 306 }
307
f996e1ed 308 foreach my $view ( $schema->get_views ) {
309 my (@comments, $view_name);
310
311 $view_name = $view->name();
312 push @comments, "--\n-- View: $view_name\n--" unless $no_comments;
313
314 # text of view is already a 'create view' statement so no need
315 # to do anything fancy.
316
317 $output .= join("\n\n",
318 @comments,
319 $view->sql(),
320 );
321 }
322
323
324 foreach my $procedure ( $schema->get_procedures ) {
325 my (@comments, $procedure_name);
326
327 $procedure_name = $procedure->name();
ea93df61 328 push @comments,
75c75c55 329 "--\n-- Procedure: $procedure_name\n--" unless $no_comments;
f996e1ed 330
75c75c55 331 # text of procedure already has the 'create procedure' stuff
332 # so there is no need to do anything fancy. However, we should
333 # think about doing fancy stuff with granting permissions and
334 # so on.
f996e1ed 335
336 $output .= join("\n\n",
337 @comments,
338 $procedure->sql(),
339 );
340 }
341
d9b22bfe 342 if ( $WARN ) {
343 if ( %truncated ) {
344 warn "Truncated " . keys( %truncated ) . " names:\n";
345 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
346 }
347
348 if ( %unreserve ) {
349 warn "Encounted " . keys( %unreserve ) .
350 " unsafe names in schema (reserved or invalid):\n";
351 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
352 }
353 }
354
355 return $output;
356}
357
d9b22bfe 358sub mk_name {
ea93df61 359 my $basename = shift || '';
360 my $type = shift || '';
361 my $scope = shift || '';
54c9812d 362 my $critical = shift || '';
d9b22bfe 363 my $basename_orig = $basename;
ea93df61 364 my $max_name = $type
365 ? $max_id_length - (length($type) + 1)
d9b22bfe 366 : $max_id_length;
ea93df61 367 $basename = substr( $basename, 0, $max_name )
d9b22bfe 368 if length( $basename ) > $max_name;
369 my $name = $type ? "${type}_$basename" : $basename;
54c9812d 370
d9b22bfe 371 if ( $basename ne $basename_orig and $critical ) {
372 my $show_type = $type ? "+'$type'" : "";
373 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
374 "character limit to make '$name'\n" if $WARN;
375 $truncated{ $basename_orig } = $name;
376 }
377
378 $scope ||= \%global_names;
379 if ( my $prev = $scope->{ $name } ) {
380 my $name_orig = $name;
381 $name .= sprintf( "%02d", ++$prev );
ea93df61 382 substr($name, $max_id_length - 3) = "00"
d9b22bfe 383 if length( $name ) > $max_id_length;
384
385 warn "The name '$name_orig' has been changed to ",
386 "'$name' to make it unique.\n" if $WARN;
387
388 $scope->{ $name_orig }++;
389 }
ea93df61 390 $name = substr( $name, 0, $max_id_length )
d9b22bfe 391 if ((length( $name ) > $max_id_length) && $critical);
392 $scope->{ $name }++;
393 return $name;
394}
395
d9b22bfe 396sub unreserve {
54c9812d 397 my $name = shift || '';
398 my $schema_obj_name = shift || '';
d9b22bfe 399 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
400
401 # also trap fields that don't begin with a letter
ea93df61 402 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
d9b22bfe 403
404 if ( $schema_obj_name ) {
405 ++$unreserve{"$schema_obj_name.$name"};
406 }
407 else {
408 ++$unreserve{"$name (table name)"};
409 }
410
411 my $unreserve = sprintf '%s_', $name;
412 return $unreserve.$suffix;
413}
414
4151;
416
d9b22bfe 417=pod
418
75c75c55 419=head1 SEE ALSO
420
421SQL::Translator.
422
590f4d4a 423=head1 AUTHORS
d9b22bfe 424
590f4d4a 425Sam Angiuoli E<lt>angiuoli@users.sourceforge.netE<gt>,
75c75c55 426Paul Harrington E<lt>harringp@deshaw.comE<gt>,
f997b9ab 427Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
d9b22bfe 428
429=cut