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   5566 use strict;
  1         3  
  1         41  
21 1     1   8 use warnings;
  1         2  
  1         89  
22             our ( $DEBUG, $WARN );
23             our $VERSION = '1.63';
24             $DEBUG = 1 unless defined $DEBUG;
25              
26 1     1   9 use Data::Dumper;
  1         2  
  1         75  
27 1     1   9 use SQL::Translator::Schema::Constants;
  1         3  
  1         88  
28 1     1   8 use SQL::Translator::Utils qw(debug header_comment);
  1         4  
  1         2008  
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         6 $DEBUG = $translator->debug;
121 1         52 $WARN = $translator->show_warnings;
122 1         35 my $no_comments = $translator->no_comments;
123 1         58 my $add_drop_table = $translator->add_drop_table;
124 1         29 my $schema = $translator->schema;
125              
126 1         15 my @output;
127 1 50       6 push @output, header_comment unless ($no_comments);
128              
129 1         5 my @foreign_keys;
130              
131 1         6 for my $table ( $schema->get_tables ) {
132 4 50       98 my $table_name = $table->name or next;
133 4         117 $table_name = mk_name( $table_name, '', undef, 1 );
134 4   50     15 my $table_name_ur = unreserve($table_name) || '';
135              
136 4         14 my ( @comments, @field_defs, @index_defs, @constraint_defs );
137              
138 4 50       13 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
139              
140 4         98 push @comments, map { "-- $_" } $table->comments;
  0         0  
141              
142             #
143             # Fields
144             #
145 4         9 my %field_name_scope;
146 4         16 for my $field ( $table->get_fields ) {
147 14         303 my $field_name = mk_name(
148             $field->name, '', \%field_name_scope, undef,1
149             );
150 14         36 my $field_name_ur = unreserve( $field_name, $table_name );
151 14         41 my $field_def = qq["$field_name_ur"];
152 14         55 $field_def =~ s/\"//g;
153 14 50       44 if ( $field_def =~ /identity/ ){
154 0         0 $field_def =~ s/identity/pidentity/;
155             }
156              
157             #
158             # Datatype
159             #
160 14         60 my $data_type = lc $field->data_type;
161 14         29 my $orig_data_type = $data_type;
162 14         325 my %extra = $field->extra;
163 14   50     66 my $list = $extra{'list'} || [];
164             # \todo deal with embedded quotes
165 14         44 my $commalist = join( ', ', map { qq['$_'] } @$list );
  0         0  
166 14         23 my $seq_name;
167              
168 14         26 my $identity = '';
169              
170 14 50       42 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       251 if ( $field->is_auto_increment ) {
183 3         28 $identity = 'IDENTITY';
184             }
185 14 100       130 if ( defined $translate{ $data_type } ) {
186 12         30 $data_type = $translate{ $data_type };
187             }
188             else {
189 2 50       9 warn "Unknown datatype: $data_type ",
190             "($table_name.$field_name)\n" if $WARN;
191             }
192             }
193              
194 14         258 my $size = $field->size;
195 14 100       174 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         38 $field_def .= " $data_type";
215 14 100       41 $field_def .= "($size)" if $size;
216 14 100       41 $field_def .= " $identity" if $identity;
217              
218             #
219             # Default value
220             #
221 14         81 my $default = $field->default_value;
222 14 100       37 if ( defined $default ) {
223 7 50 33     134 $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       375 unless ( $field->is_nullable ) {
234 5         318 $field_def .= ' NOT NULL';
235             }
236             else {
237 9 50       290 $field_def .= ' NULL' if $data_type ne 'bit';
238             }
239              
240 14         66 push @field_defs, $field_def;
241             }
242              
243             #
244             # Constraint Declarations
245             #
246 4         15 my @constraint_decs = ();
247 4         8 my $c_name_default;
248 4         20 for my $constraint ( $table->get_constraints ) {
249 5   100     175 my $name = $constraint->name || '';
250 5   50     97 my $type = $constraint->type || NORMAL;
251 5         134 my @fields = map { unreserve( $_, $table_name ) }
  5         18  
252             $constraint->fields;
253 5         214 my @rfields = map { unreserve( $_, $table_name ) }
  1         5  
254             $constraint->reference_fields;
255 5 50       30 next unless @fields;
256              
257 5 100       27 if ( $type eq PRIMARY_KEY ) {
    100          
    50          
258 3   33     22 $name ||= mk_name( $table_name, 'pk', undef,1 );
259 3         59 push @constraint_defs,
260             "CONSTRAINT $name PRIMARY KEY ".
261             '(' . join( ', ', @fields ) . ')';
262             }
263             elsif ( $type eq FOREIGN_KEY ) {
264 1   33     5 $name ||= mk_name( $table_name, 'fk', undef,1 );
265 1         56 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     5 $name ||= mk_name(
      33        
273             $table_name,
274             $name || ++$c_name_default,undef, 1
275             );
276 1         10 push @constraint_defs,
277             "CONSTRAINT $name UNIQUE " .
278             '(' . join( ', ', @fields ) . ')';
279             }
280             }
281              
282             #
283             # Indices
284             #
285 4         80 for my $index ( $table->get_indices ) {
286 1         32 push @index_defs,
287             'CREATE INDEX ' . $index->name .
288             " ON $table_name (".
289             join( ', ', $index->fields ) . ")";
290             }
291              
292 4 50       20 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         17 map { " $_" } @field_defs, @constraint_defs
  18         78  
297             ).
298             "\n)"
299             ;
300              
301 4         869 $create_statement = join("\n\n", @comments) . "\n\n" . $create_statement;
302 4         23 push @output,
303             $create_statement,
304             @index_defs,
305             ;
306             }
307              
308 1         9 foreach my $view ( $schema->get_views ) {
309 1         4 my (@comments, $view_name);
310              
311 1         6 $view_name = $view->name();
312 1 50       5 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         9 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         4 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         8 push @output, join("\n\n",
337             @comments,
338             $procedure->sql(),
339             );
340             }
341 1         3 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       17 return wantarray ? @output : join ";\n\n", @output;
357             }
358              
359             sub mk_name {
360 21   50 21 0 372 my $basename = shift || '';
361 21   100     92 my $type = shift || '';
362 21   100     67 my $scope = shift || '';
363 21   100     71 my $critical = shift || '';
364 21         42 my $basename_orig = $basename;
365 21 100       59 my $max_name = $type
366             ? $max_id_length - (length($type) + 1)
367             : $max_id_length;
368 21 50       60 $basename = substr( $basename, 0, $max_name )
369             if length( $basename ) > $max_name;
370 21 100       51 my $name = $type ? "${type}_$basename" : $basename;
371              
372 21 50 33     72 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     73 $scope ||= \%global_names;
380 21 50       63 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     62 $name = substr( $name, 0, $max_id_length )
392             if ((length( $name ) > $max_id_length) && $critical);
393 21         80 $scope->{ $name }++;
394 21         60 return $name;
395             }
396              
397             sub unreserve {
398 24   50 24 0 68 my $name = shift || '';
399 24   100     204 my $schema_obj_name = shift || '';
400 24 100       109 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
401              
402             # also trap fields that don't begin with a letter
403 24 100 66     272 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
404              
405 5 100       15 if ( $schema_obj_name ) {
406 3         13 ++$unreserve{"$schema_obj_name.$name"};
407             }
408             else {
409 2         9 ++$unreserve{"$name (table name)"};
410             }
411              
412 5         22 my $unreserve = sprintf '%s_', $name;
413 5         20 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