File Coverage

blib/lib/SQL/Translator/Producer/Sybase.pm
Criterion Covered Total %
statement 125 149 83.8
branch 54 90 60.0
condition 26 49 53.0
subroutine 8 8 100.0
pod 0 3 0.0
total 213 299 71.2


line stmt bran cond sub pod time code
1             package SQL::Translator::Producer::Sybase;
2              
3             =head1 NAME
4              
5             SQL::Translator::Producer::Sybase - Sybase producer for SQL::Translator
6              
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              
16             This module will produce text output of the schema suitable for Sybase.
17              
18             =cut
19              
20 1     1   4211 use strict;
  1         3  
  1         34  
21 1     1   5 use warnings;
  1         3  
  1         71  
22             our ( $DEBUG, $WARN );
23             our $VERSION = '1.6_3';
24             $DEBUG = 1 unless defined $DEBUG;
25              
26 1     1   5 use Data::Dumper;
  1         2  
  1         60  
27 1     1   5 use SQL::Translator::Schema::Constants;
  1         2  
  1         143  
28 1     1   7 use SQL::Translator::Utils qw(debug header_comment);
  1         2  
  1         1589  
29              
30             my %translate = (
31             #
32             # Sybase types
33             #
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',
47             serial => 'numeric',
48             boolean => 'varchar',
49             char => 'char',
50             long => 'varchar',
51             );
52              
53             my %reserved = map { $_, 1 } qw[
54             ALL ANALYSE ANALYZE AND ANY AS ASC
55             BETWEEN BINARY BOTH
56             CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
57             CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
58             DEFAULT DEFERRABLE DESC DISTINCT DO
59             ELSE END EXCEPT
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
64             NATURAL NEW NOT NOTNULL NULL
65             OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
66             PRIMARY PUBLIC REFERENCES RIGHT
67             SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
68             UNION UNIQUE USER USING VERBOSE WHEN WHERE
69             ];
70              
71             my $max_id_length = 30;
72             my %used_identifiers = ();
73             my %global_names;
74             my %unreserve;
75             my %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              
88             where 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              
97             and 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              
118             sub produce {
119 1     1 0 3 my $translator = shift;
120 1         4 $DEBUG = $translator->debug;
121 1         22 $WARN = $translator->show_warnings;
122 1         20 my $no_comments = $translator->no_comments;
123 1         25 my $add_drop_table = $translator->add_drop_table;
124 1         23 my $schema = $translator->schema;
125              
126 1         9 my @output;
127 1 50       4 push @output, header_comment unless ($no_comments);
128              
129 1         3 my @foreign_keys;
130              
131 1         7 for my $table ( $schema->get_tables ) {
132 4 50       81 my $table_name = $table->name or next;
133 4         94 $table_name = mk_name( $table_name, '', undef, 1 );
134 4   50     15 my $table_name_ur = unreserve($table_name) || '';
135              
136 4         12 my ( @comments, @field_defs, @index_defs, @constraint_defs );
137              
138 4 50       15 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
139              
140 4         86 push @comments, map { "-- $_" } $table->comments;
  0         0  
141              
142             #
143             # Fields
144             #
145 4         8 my %field_name_scope;
146 4         19 for my $field ( $table->get_fields ) {
147 14         278 my $field_name = mk_name(
148             $field->name, '', \%field_name_scope, undef,1
149             );
150 14         38 my $field_name_ur = unreserve( $field_name, $table_name );
151 14         37 my $field_def = qq["$field_name_ur"];
152 14         51 $field_def =~ s/\"//g;
153 14 50       40 if ( $field_def =~ /identity/ ){
154 0         0 $field_def =~ s/identity/pidentity/;
155             }
156              
157             #
158             # Datatype
159             #
160 14         58 my $data_type = lc $field->data_type;
161 14         27 my $orig_data_type = $data_type;
162 14         269 my %extra = $field->extra;
163 14   50     64 my $list = $extra{'list'} || [];
164             # \todo deal with embedded quotes
165 14         34 my $commalist = join( ', ', map { qq['$_'] } @$list );
  0         0  
166 14         20 my $seq_name;
167              
168 14         24 my $identity = '';
169              
170 14 50       54 if ( $data_type eq 'enum' ) {
    50          
171 0         0 my $check_name = mk_name(
172             $table_name.'_'.$field_name, 'chk' ,undef, 1
173             );
174 0         0 push @constraint_defs,
175             "CONSTRAINT $check_name CHECK ($field_name IN ($commalist))";
176 0         0 $data_type .= 'character varying';
177             }
178             elsif ( $data_type eq 'set' ) {
179 0         0 $data_type .= 'character varying';
180             }
181             else {
182 14 100       208 if ( $field->is_auto_increment ) {
183 3         31 $identity = 'IDENTITY';
184             }
185 14 100       100 if ( defined $translate{ $data_type } ) {
186 12         30 $data_type = $translate{ $data_type };
187             }
188             else {
189 2 50       7 warn "Unknown datatype: $data_type ",
190             "($table_name.$field_name)\n" if $WARN;
191             }
192             }
193              
194 14         237 my $size = $field->size;
195 14 100       135 unless ( $size ) {
196 3 50 33     27 if ( $data_type =~ /numeric/ ) {
    100          
    50          
    50          
197 0         0 $size = '9,0';
198             }
199             elsif ( $orig_data_type eq 'text' ) {
200             #interpret text fields as long varchars
201 2         6 $size = '255';
202             }
203             elsif (
204             $data_type eq 'varchar' &&
205             $orig_data_type eq 'boolean'
206             ) {
207 0         0 $size = '6';
208             }
209             elsif ( $data_type eq 'varchar' ) {
210 0         0 $size = '255';
211             }
212             }
213              
214 14         35 $field_def .= " $data_type";
215 14 100       50 $field_def .= "($size)" if $size;
216 14 100       37 $field_def .= " $identity" if $identity;
217              
218             #
219             # Default value
220             #
221 14         38 my $default = $field->default_value;
222 14 100       41 if ( defined $default ) {
223 7 50 33     100 $field_def .= sprintf( ' DEFAULT %s',
    50          
224             ( $field->is_auto_increment && $seq_name )
225             ? qq[nextval('"$seq_name"'::text)] :
226             ( $default =~ m/null/i ) ? 'NULL' : "'$default'"
227             );
228             }
229              
230             #
231             # Not null constraint
232             #
233 14 100       299 unless ( $field->is_nullable ) {
234 5         208 $field_def .= ' NOT NULL';
235             }
236             else {
237 9 50       194 $field_def .= ' NULL' if $data_type ne 'bit';
238             }
239              
240 14         49 push @field_defs, $field_def;
241             }
242              
243             #
244             # Constraint Declarations
245             #
246 4         10 my @constraint_decs = ();
247 4         7 my $c_name_default;
248 4         24 for my $constraint ( $table->get_constraints ) {
249 5   100     151 my $name = $constraint->name || '';
250 5   50     89 my $type = $constraint->type || NORMAL;
251 5         114 my @fields = map { unreserve( $_, $table_name ) }
  5         21  
252             $constraint->fields;
253 5         178 my @rfields = map { unreserve( $_, $table_name ) }
  1         4  
254             $constraint->reference_fields;
255 5 50       17 next unless @fields;
256              
257 5 100       28 if ( $type eq PRIMARY_KEY ) {
    100          
    50          
258 3   33     23 $name ||= mk_name( $table_name, 'pk', undef,1 );
259 3         26 push @constraint_defs,
260             "CONSTRAINT $name PRIMARY KEY ".
261             '(' . join( ', ', @fields ) . ')';
262             }
263             elsif ( $type eq FOREIGN_KEY ) {
264 1   33     4 $name ||= mk_name( $table_name, 'fk', undef,1 );
265 1         54 push @foreign_keys,
266             "ALTER TABLE $table ADD CONSTRAINT $name FOREIGN KEY".
267             ' (' . join( ', ', @fields ) . ') REFERENCES '.
268             $constraint->reference_table.
269             ' (' . join( ', ', @rfields ) . ')';
270             }
271             elsif ( $type eq UNIQUE ) {
272 1   0     4 $name ||= mk_name(
      33        
273             $table_name,
274             $name || ++$c_name_default,undef, 1
275             );
276 1         7 push @constraint_defs,
277             "CONSTRAINT $name UNIQUE " .
278             '(' . join( ', ', @fields ) . ')';
279             }
280             }
281              
282             #
283             # Indices
284             #
285 4         59 for my $index ( $table->get_indices ) {
286 1         27 push @index_defs,
287             'CREATE INDEX ' . $index->name .
288             " ON $table_name (".
289             join( ', ', $index->fields ) . ")";
290             }
291              
292 4 50       21 my $drop_statement = $add_drop_table
293             ? qq[DROP TABLE $table_name_ur] : '';
294             my $create_statement = qq[CREATE TABLE $table_name_ur (\n].
295             join( ",\n",
296 4         16 map { " $_" } @field_defs, @constraint_defs
  18         60  
297             ).
298             "\n)"
299             ;
300              
301 4         15 $create_statement = join("\n\n", @comments) . "\n\n" . $create_statement;
302 4         25 push @output,
303             $create_statement,
304             @index_defs,
305             ;
306             }
307              
308 1         7 foreach my $view ( $schema->get_views ) {
309 1         5 my (@comments, $view_name);
310              
311 1         5 $view_name = $view->name();
312 1 50       4 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 1         8 push @output, join("\n\n",
318             @comments,
319             $view->sql(),
320             );
321             }
322              
323              
324 1         6 foreach my $procedure ( $schema->get_procedures ) {
325 1         3 my (@comments, $procedure_name);
326              
327 1         6 $procedure_name = $procedure->name();
328 1 50       4 push @comments,
329             "--\n-- Procedure: $procedure_name\n--" unless $no_comments;
330              
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.
335              
336 1         7 push @output, join("\n\n",
337             @comments,
338             $procedure->sql(),
339             );
340             }
341 1         4 push @output, @foreign_keys;
342              
343 1 50       5 if ( $WARN ) {
344 0 0       0 if ( %truncated ) {
345 0         0 warn "Truncated " . keys( %truncated ) . " names:\n";
346 0         0 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
347             }
348              
349 0 0       0 if ( %unreserve ) {
350 0         0 warn "Encounted " . keys( %unreserve ) .
351             " unsafe names in schema (reserved or invalid):\n";
352 0         0 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
353             }
354             }
355              
356 1 50       212 return wantarray ? @output : join ";\n\n", @output;
357             }
358              
359             sub mk_name {
360 21   50 21 0 298 my $basename = shift || '';
361 21   100     86 my $type = shift || '';
362 21   100     58 my $scope = shift || '';
363 21   100     68 my $critical = shift || '';
364 21         36 my $basename_orig = $basename;
365 21 100       56 my $max_name = $type
366             ? $max_id_length - (length($type) + 1)
367             : $max_id_length;
368 21 50       53 $basename = substr( $basename, 0, $max_name )
369             if length( $basename ) > $max_name;
370 21 100       112 my $name = $type ? "${type}_$basename" : $basename;
371              
372 21 50 33     63 if ( $basename ne $basename_orig and $critical ) {
373 0 0       0 my $show_type = $type ? "+'$type'" : "";
374 0 0       0 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
375             "character limit to make '$name'\n" if $WARN;
376 0         0 $truncated{ $basename_orig } = $name;
377             }
378              
379 21   100     63 $scope ||= \%global_names;
380 21 50       60 if ( my $prev = $scope->{ $name } ) {
381 0         0 my $name_orig = $name;
382 0         0 $name .= sprintf( "%02d", ++$prev );
383 0 0       0 substr($name, $max_id_length - 3) = "00"
384             if length( $name ) > $max_id_length;
385              
386 0 0       0 warn "The name '$name_orig' has been changed to ",
387             "'$name' to make it unique.\n" if $WARN;
388              
389 0         0 $scope->{ $name_orig }++;
390             }
391 21 50 33     59 $name = substr( $name, 0, $max_id_length )
392             if ((length( $name ) > $max_id_length) && $critical);
393 21         55 $scope->{ $name }++;
394 21         51 return $name;
395             }
396              
397             sub unreserve {
398 24   50 24 0 58 my $name = shift || '';
399 24   100     139 my $schema_obj_name = shift || '';
400 24 100       82 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
401              
402             # also trap fields that don't begin with a letter
403 24 100 66     217 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
404              
405 5 100       17 if ( $schema_obj_name ) {
406 3         12 ++$unreserve{"$schema_obj_name.$name"};
407             }
408             else {
409 2         9 ++$unreserve{"$name (table name)"};
410             }
411              
412 5         23 my $unreserve = sprintf '%s_', $name;
413 5         18 return $unreserve.$suffix;
414             }
415              
416             1;
417              
418             =pod
419              
420             =head1 SEE ALSO
421              
422             SQL::Translator.
423              
424             =head1 AUTHORS
425              
426             Sam Angiuoli Eangiuoli@users.sourceforge.netE,
427             Paul Harrington Eharringp@deshaw.comE,
428             Ken Youens-Clark Ekclark@cpan.orgE.
429              
430             =cut