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   7494 use strict;
  1         3  
  1         48  
21 1     1   35 use warnings;
  1         3  
  1         145  
22             our ($DEBUG, $WARN);
23             our $VERSION = '1.66';
24             $DEBUG = 1 unless defined $DEBUG;
25              
26 1     1   8 use Data::Dumper;
  1         2  
  1         84  
27 1     1   9 use SQL::Translator::Schema::Constants;
  1         2  
  1         112  
28 1     1   7 use SQL::Translator::Utils qw(debug header_comment);
  1         3  
  1         2859  
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         5 $DEBUG = $translator->debug;
121 1         37 $WARN = $translator->show_warnings;
122 1         32 my $no_comments = $translator->no_comments;
123 1         29 my $add_drop_table = $translator->add_drop_table;
124 1         32 my $schema = $translator->schema;
125              
126 1         11 my @output;
127 1 50       6 push @output, header_comment unless ($no_comments);
128              
129 1         3 my @foreign_keys;
130              
131 1         9 for my $table ($schema->get_tables) {
132 4 50       124 my $table_name = $table->name or next;
133 4         154 $table_name = mk_name($table_name, '', undef, 1);
134 4   50     18 my $table_name_ur = unreserve($table_name) || '';
135              
136 4         13 my (@comments, @field_defs, @index_defs, @constraint_defs);
137              
138 4 50       14 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
139              
140 4         149 push @comments, map {"-- $_"} $table->comments;
  0         0  
141              
142             #
143             # Fields
144             #
145 4         11 my %field_name_scope;
146 4         20 for my $field ($table->get_fields) {
147 14         404 my $field_name = mk_name($field->name, '', \%field_name_scope, undef, 1);
148 14         61 my $field_name_ur = unreserve($field_name, $table_name);
149 14         47 my $field_def = qq["$field_name_ur"];
150 14         68 $field_def =~ s/\"//g;
151 14 50       55 if ($field_def =~ /identity/) {
152 0         0 $field_def =~ s/identity/pidentity/;
153             }
154              
155             #
156             # Datatype
157             #
158 14         72 my $data_type = lc $field->data_type;
159 14         29 my $orig_data_type = $data_type;
160 14         541 my %extra = $field->extra;
161 14   50     113 my $list = $extra{'list'} || [];
162              
163             # \todo deal with embedded quotes
164 14         54 my $commalist = join(', ', map {qq['$_']} @$list);
  0         0  
165 14         28 my $seq_name;
166              
167 14         32 my $identity = '';
168              
169 14 50       67 if ($data_type eq 'enum') {
    50          
170 0         0 my $check_name = mk_name($table_name . '_' . $field_name, 'chk', undef, 1);
171 0         0 push @constraint_defs, "CONSTRAINT $check_name CHECK ($field_name IN ($commalist))";
172 0         0 $data_type .= 'character varying';
173             } elsif ($data_type eq 'set') {
174 0         0 $data_type .= 'character varying';
175             } else {
176 14 100       361 if ($field->is_auto_increment) {
177 3         33 $identity = 'IDENTITY';
178             }
179 14 100       132 if (defined $translate{$data_type}) {
180 12         37 $data_type = $translate{$data_type};
181             } else {
182 2 50       10 warn "Unknown datatype: $data_type ", "($table_name.$field_name)\n"
183             if $WARN;
184             }
185             }
186              
187 14         401 my $size = $field->size;
188 14 100       187 unless ($size) {
189 3 50 33     32 if ($data_type =~ /numeric/) {
    100          
    50          
    50          
190 0         0 $size = '9,0';
191             } elsif ($orig_data_type eq 'text') {
192              
193             #interpret text fields as long varchars
194 2         8 $size = '255';
195             } elsif ($data_type eq 'varchar'
196             && $orig_data_type eq 'boolean') {
197 0         0 $size = '6';
198             } elsif ($data_type eq 'varchar') {
199 0         0 $size = '255';
200             }
201             }
202              
203 14         40 $field_def .= " $data_type";
204 14 100       53 $field_def .= "($size)" if $size;
205 14 100       49 $field_def .= " $identity" if $identity;
206              
207             #
208             # Default value
209             #
210 14         74 my $default = $field->default_value;
211 14 100       54 if (defined $default) {
212 7 50 33     167 $field_def .= sprintf(' DEFAULT %s',
    50          
213             ($field->is_auto_increment && $seq_name) ? qq[nextval('"$seq_name"'::text)]
214             : ($default =~ m/null/i) ? 'NULL'
215             : "'$default'");
216             }
217              
218             #
219             # Not null constraint
220             #
221 14 100       505 unless ($field->is_nullable) {
222 5         274 $field_def .= ' NOT NULL';
223             } else {
224 9 50       324 $field_def .= ' NULL' if $data_type ne 'bit';
225             }
226              
227 14         75 push @field_defs, $field_def;
228             }
229              
230             #
231             # Constraint Declarations
232             #
233 4         14 my @constraint_decs = ();
234 4         9 my $c_name_default;
235 4         25 for my $constraint ($table->get_constraints) {
236 5   100     254 my $name = $constraint->name || '';
237 5   50     156 my $type = $constraint->type || NORMAL;
238 5         166 my @fields = map { unreserve($_, $table_name) } $constraint->fields;
  5         43  
239 5         283 my @rfields = map { unreserve($_, $table_name) } $constraint->reference_fields;
  1         7  
240 5 50       22 next unless @fields;
241              
242 5 100       28 if ($type eq PRIMARY_KEY) {
    100          
    50          
243 3   33     23 $name ||= mk_name($table_name, 'pk', undef, 1);
244 3         23 push @constraint_defs, "CONSTRAINT $name PRIMARY KEY " . '(' . join(', ', @fields) . ')';
245             } elsif ($type eq FOREIGN_KEY) {
246 1   33     5 $name ||= mk_name($table_name, 'fk', undef, 1);
247 1         73 push @foreign_keys,
248             "ALTER TABLE $table ADD CONSTRAINT $name FOREIGN KEY" . ' ('
249             . join(', ', @fields)
250             . ') REFERENCES '
251             . $constraint->reference_table . ' ('
252             . join(', ', @rfields) . ')';
253             } elsif ($type eq UNIQUE) {
254 1   0     5 $name ||= mk_name($table_name, $name || ++$c_name_default, undef, 1);
      33        
255 1         8 push @constraint_defs, "CONSTRAINT $name UNIQUE " . '(' . join(', ', @fields) . ')';
256             }
257             }
258              
259             #
260             # Indices
261             #
262 4         93 for my $index ($table->get_indices) {
263 1         36 push @index_defs, 'CREATE INDEX ' . $index->name . " ON $table_name (" . join(', ', $index->fields) . ")";
264             }
265              
266 4 50       24 my $drop_statement = $add_drop_table ? qq[DROP TABLE $table_name_ur] : '';
267             my $create_statement
268 4         17 = qq[CREATE TABLE $table_name_ur (\n] . join(",\n", map {" $_"} @field_defs, @constraint_defs) . "\n)";
  18         65  
269              
270 4         20 $create_statement = join("\n\n", @comments) . "\n\n" . $create_statement;
271 4         26 push @output, $create_statement, @index_defs,;
272             }
273              
274 1         37 foreach my $view ($schema->get_views) {
275 1         4 my (@comments, $view_name);
276              
277 1         7 $view_name = $view->name();
278 1 50       5 push @comments, "--\n-- View: $view_name\n--" unless $no_comments;
279              
280             # text of view is already a 'create view' statement so no need
281             # to do anything fancy.
282              
283 1         12 push @output, join("\n\n", @comments, $view->sql(),);
284             }
285              
286 1         7 foreach my $procedure ($schema->get_procedures) {
287 1         5 my (@comments, $procedure_name);
288              
289 1         7 $procedure_name = $procedure->name();
290 1 50       5 push @comments, "--\n-- Procedure: $procedure_name\n--"
291             unless $no_comments;
292              
293             # text of procedure already has the 'create procedure' stuff
294             # so there is no need to do anything fancy. However, we should
295             # think about doing fancy stuff with granting permissions and
296             # so on.
297              
298 1         8 push @output, join("\n\n", @comments, $procedure->sql(),);
299             }
300 1         5 push @output, @foreign_keys;
301              
302 1 50       5 if ($WARN) {
303 0 0       0 if (%truncated) {
304 0         0 warn "Truncated " . keys(%truncated) . " names:\n";
305 0         0 warn "\t" . join("\n\t", sort keys %truncated) . "\n";
306             }
307              
308 0 0       0 if (%unreserve) {
309 0         0 warn "Encounted " . keys(%unreserve) . " unsafe names in schema (reserved or invalid):\n";
310 0         0 warn "\t" . join("\n\t", sort keys %unreserve) . "\n";
311             }
312             }
313              
314 1 50       23 return wantarray ? @output : join ";\n\n", @output;
315             }
316              
317             sub mk_name {
318 21   50 21 0 483 my $basename = shift || '';
319 21   100     107 my $type = shift || '';
320 21   100     443 my $scope = shift || '';
321 21   100     98 my $critical = shift || '';
322 21         51 my $basename_orig = $basename;
323 21 100       66 my $max_name
324             = $type
325             ? $max_id_length - (length($type) + 1)
326             : $max_id_length;
327 21 50       66 $basename = substr($basename, 0, $max_name)
328             if length($basename) > $max_name;
329 21 100       69 my $name = $type ? "${type}_$basename" : $basename;
330              
331 21 50 33     80 if ($basename ne $basename_orig and $critical) {
332 0 0       0 my $show_type = $type ? "+'$type'" : "";
333 0 0       0 warn "Truncating '$basename_orig'$show_type to $max_id_length ", "character limit to make '$name'\n"
334             if $WARN;
335 0         0 $truncated{$basename_orig} = $name;
336             }
337              
338 21   100     75 $scope ||= \%global_names;
339 21 50       81 if (my $prev = $scope->{$name}) {
340 0         0 my $name_orig = $name;
341 0         0 $name .= sprintf("%02d", ++$prev);
342 0 0       0 substr($name, $max_id_length - 3) = "00"
343             if length($name) > $max_id_length;
344              
345 0 0       0 warn "The name '$name_orig' has been changed to ", "'$name' to make it unique.\n"
346             if $WARN;
347              
348 0         0 $scope->{$name_orig}++;
349             }
350 21 50 33     80 $name = substr($name, 0, $max_id_length)
351             if ((length($name) > $max_id_length) && $critical);
352 21         77 $scope->{$name}++;
353 21         95 return $name;
354             }
355              
356             sub unreserve {
357 24   50 24 0 88 my $name = shift || '';
358 24   100     222 my $schema_obj_name = shift || '';
359 24 100       128 my ($suffix) = ($name =~ s/(\W.*)$//) ? $1 : '';
360              
361             # also trap fields that don't begin with a letter
362 24 100 66     373 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
363              
364 5 100       17 if ($schema_obj_name) {
365 3         14 ++$unreserve{"$schema_obj_name.$name"};
366             } else {
367 2         9 ++$unreserve{"$name (table name)"};
368             }
369              
370 5         30 my $unreserve = sprintf '%s_', $name;
371 5         26 return $unreserve . $suffix;
372             }
373              
374             1;
375              
376             =pod
377              
378             =head1 SEE ALSO
379              
380             SQL::Translator.
381              
382             =head1 AUTHORS
383              
384             Sam Angiuoli Eangiuoli@users.sourceforge.netE,
385             Paul Harrington Eharringp@deshaw.comE,
386             Ken Youens-Clark Ekclark@cpan.orgE.
387              
388             =cut