File Coverage

blib/lib/SQL/Translator/Producer/DB2.pm
Criterion Covered Total %
statement 108 110 98.1
branch 39 60 65.0
condition 9 19 47.3
subroutine 17 17 100.0
pod 0 11 0.0
total 173 217 79.7


line stmt bran cond sub pod time code
1             package SQL::Translator::Producer::DB2;
2              
3             =head1 NAME
4              
5             SQL::Translator::Producer::DB2 - DB2 SQL producer
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator;
10              
11             my $t = SQL::Translator->new( parser => '...', producer => 'DB2' );
12             print $translator->translate( $file );
13              
14             =head1 DESCRIPTION
15              
16             Creates an SQL DDL suitable for DB2.
17              
18             =cut
19              
20 4     4   3962 use warnings;
  4         422  
  4         326  
21 4     4   33 use strict;
  4         9  
  4         120  
22 4     4   19 use warnings;
  4         9  
  4         471  
23             our ($DEBUG, $WARN);
24             our $VERSION = '1.66';
25             $DEBUG = 0 unless defined $DEBUG;
26              
27 4     4   30 use SQL::Translator::Schema::Constants;
  4         10  
  4         632  
28 4     4   691 use SQL::Translator::Utils qw(header_comment);
  4         11  
  4         905  
29              
30             # http://publib.boulder.ibm.com/infocenter/db2help/topic/com.ibm.db2.udb.doc/ad/r0006844.htm
31              
32             # This is a terrible WTDI, each Parser should parse down to some standard set
33             # of SQL data types, with field->extra entries being used to convert back to
34             # weird types like "polygon" if needed (IMO anyway)
35              
36             my %dt_translate;
37              
38             BEGIN {
39 4     4   11650 %dt_translate = (
40             #
41             # MySQL types
42             #
43             int => 'integer',
44             mediumint => 'integer',
45             tinyint => 'smallint',
46             char => 'char',
47             tinyblob => 'blob',
48             mediumblob => 'blob',
49             longblob => 'long varchar for bit data',
50             tinytext => 'varchar',
51             text => 'varchar',
52             longtext => 'varchar',
53             mediumtext => 'varchar',
54             enum => 'varchar',
55             set => 'varchar',
56             date => 'date',
57             datetime => 'timestamp',
58             time => 'time',
59             year => 'date',
60              
61             #
62             # PostgreSQL types
63             #
64             'double precision' => 'double',
65             serial => 'integer',
66             bigserial => 'integer',
67             money => 'double',
68             character => 'char',
69             'character varying' => 'varchar',
70             bytea => 'BLOB',
71             interval => 'integer',
72             boolean => 'smallint',
73             point => 'integer',
74             line => 'integer',
75             lseg => 'integer',
76             box => 'integer',
77             path => 'integer',
78             polygon => 'integer',
79             circle => 'integer',
80             cidr => 'integer',
81             inet => 'varchar',
82             macaddr => 'varchar',
83             bit => 'number',
84             'bit varying' => 'number',
85              
86             #
87             # DB types
88             #
89             number => 'integer',
90             varchar2 => 'varchar',
91             long => 'clob',
92             );
93             }
94              
95             my %db2_reserved = map { $_ => 1 } qw/
96             ADD DETERMINISTIC LEAVE RESTART
97             AFTER DISALLOW LEFT RESTRICT
98             ALIAS DISCONNECT LIKE RESULT
99             ALL DISTINCT LINKTYPE RESULT_SET_LOCATOR
100             ALLOCATE DO LOCAL RETURN
101             ALLOW DOUBLE LOCALE RETURNS
102             ALTER DROP LOCATOR REVOKE
103             AND DSNHATTR LOCATORS RIGHT
104             ANY DSSIZE LOCK ROLLBACK
105             APPLICATION DYNAMIC LOCKMAX ROUTINE
106             AS EACH LOCKSIZE ROW
107             ASSOCIATE EDITPROC LONG ROWS
108             ASUTIME ELSE LOOP RRN
109             AUDIT ELSEIF MAXVALUE RUN
110             AUTHORIZATION ENCODING MICROSECOND SAVEPOINT
111             AUX END MICROSECONDS SCHEMA
112             AUXILIARY END-EXEC MINUTE SCRATCHPAD
113             BEFORE END-EXEC1 MINUTES SECOND
114             BEGIN ERASE MINVALUE SECONDS
115             BETWEEN ESCAPE MODE SECQTY
116             BINARY EXCEPT MODIFIES SECURITY
117             BUFFERPOOL EXCEPTION MONTH SELECT
118             BY EXCLUDING MONTHS SENSITIVE
119             CACHE EXECUTE NEW SET
120             CALL EXISTS NEW_TABLE SIGNAL
121             CALLED EXIT NO SIMPLE
122             CAPTURE EXTERNAL NOCACHE SOME
123             CARDINALITY FENCED NOCYCLE SOURCE
124             CASCADED FETCH NODENAME SPECIFIC
125             CASE FIELDPROC NODENUMBER SQL
126             CAST FILE NOMAXVALUE SQLID
127             CCSID FINAL NOMINVALUE STANDARD
128             CHAR FOR NOORDER START
129             CHARACTER FOREIGN NOT STATIC
130             CHECK FREE NULL STAY
131             CLOSE FROM NULLS STOGROUP
132             CLUSTER FULL NUMPARTS STORES
133             COLLECTION FUNCTION OBID STYLE
134             COLLID GENERAL OF SUBPAGES
135             COLUMN GENERATED OLD SUBSTRING
136             COMMENT GET OLD_TABLE SYNONYM
137             COMMIT GLOBAL ON SYSFUN
138             CONCAT GO OPEN SYSIBM
139             CONDITION GOTO OPTIMIZATION SYSPROC
140             CONNECT GRANT OPTIMIZE SYSTEM
141             CONNECTION GRAPHIC OPTION TABLE
142             CONSTRAINT GROUP OR TABLESPACE
143             CONTAINS HANDLER ORDER THEN
144             CONTINUE HAVING OUT TO
145             COUNT HOLD OUTER TRANSACTION
146             COUNT_BIG HOUR OVERRIDING TRIGGER
147             CREATE HOURS PACKAGE TRIM
148             CROSS IDENTITY PARAMETER TYPE
149             CURRENT IF PART UNDO
150             CURRENT_DATE IMMEDIATE PARTITION UNION
151             CURRENT_LC_CTYPE IN PATH UNIQUE
152             CURRENT_PATH INCLUDING PIECESIZE UNTIL
153             CURRENT_SERVER INCREMENT PLAN UPDATE
154             CURRENT_TIME INDEX POSITION USAGE
155             CURRENT_TIMESTAMP INDICATOR PRECISION USER
156             CURRENT_TIMEZONE INHERIT PREPARE USING
157             CURRENT_USER INNER PRIMARY VALIDPROC
158             CURSOR INOUT PRIQTY VALUES
159             CYCLE INSENSITIVE PRIVILEGES VARIABLE
160             DATA INSERT PROCEDURE VARIANT
161             DATABASE INTEGRITY PROGRAM VCAT
162             DAY INTO PSID VIEW
163             DAYS IS QUERYNO VOLUMES
164             DB2GENERAL ISOBID READ WHEN
165             DB2GENRL ISOLATION READS WHERE
166             DB2SQL ITERATE RECOVERY WHILE
167             DBINFO JAR REFERENCES WITH
168             DECLARE JAVA REFERENCING WLM
169             DEFAULT JOIN RELEASE WRITE
170             DEFAULTS KEY RENAME YEAR
171             DEFINITION LABEL REPEAT YEARS
172             DELETE LANGUAGE RESET
173             DESCRIPTOR LC_CTYPE RESIGNAL
174             /;
175              
176             sub produce {
177 3     3 0 10 my ($translator) = @_;
178 3         16 $DEBUG = $translator->debug;
179 3         90 $WARN = $translator->show_warnings;
180 3         82 my $no_comments = $translator->no_comments;
181 3         104 my $add_drop_table = $translator->add_drop_table;
182 3         97 my $schema = $translator->schema;
183 3         31 my $output = '';
184 3         9 my $indent = ' ';
185              
186 3 50       12 $output .= header_comment unless ($no_comments);
187 3         10 my (@table_defs, @fks, @index_defs);
188 3         20 foreach my $table ($schema->get_tables) {
189 8 50       218 push @table_defs, 'DROP TABLE ' . $table->name . ";" if $add_drop_table;
190 8         248 my ($table_def, $fks) = create_table(
191             $table,
192             {
193             no_comments => $no_comments
194             }
195             );
196 8         34 push @table_defs, $table_def;
197 8         20 push @fks, @$fks;
198              
199 8         39 foreach my $index ($table->get_indices) {
200 3         46 push @index_defs, create_index($index);
201             }
202              
203             }
204 3         9 my (@view_defs);
205 3         25 foreach my $view ($schema->get_views) {
206 3         17 push @view_defs, create_view($view);
207             }
208 3         6 my (@trigger_defs);
209 3         18 foreach my $trigger ($schema->get_triggers) {
210 7         27 push @trigger_defs, create_trigger($trigger);
211             }
212              
213             return wantarray
214 3 100       260 ? (@table_defs, @fks, @index_defs, @view_defs, @trigger_defs)
215             : $output . join("\n\n", @table_defs, @fks, @index_defs, @view_defs, @trigger_defs) . "\n";
216             }
217              
218             {
219             my %objnames;
220              
221             sub check_name {
222 46     46 0 1236 my ($name, $type, $length) = @_;
223              
224 46         98 my $newname = $name;
225 46 50       193 if (length($name) > $length) ## Maximum table name length is 18
226             {
227 0 0       0 warn "Table name $name is longer than $length characters, truncated"
228             if $WARN;
229              
230             # if(grep {$_ eq substr($name, 0, $length) }
231             # values(%{$objnames{$type}}))
232             # {
233             # die "Got multiple matching table names when truncated";
234             # }
235             # $objnames{$type}{$name} = substr($name, 0,$length);
236             # $newname = $objnames{$type}{$name};
237             }
238              
239 46 50       206 if ($db2_reserved{ uc($newname) }) {
240 0 0       0 warn "$newname is a reserved word in DB2!" if $WARN;
241             }
242              
243             # return sprintf("%-*s", $length-5, $newname);
244 46         160 return $newname;
245             }
246             }
247              
248             sub create_table {
249 8     8 0 23 my ($table, $options) = @_;
250              
251 8         195 my $table_name = check_name($table->name, 'tables', 128);
252              
253             # this limit is 18 in older DB2s ! (<= 8)
254              
255 8         20 my (@field_defs, @comments);
256             push @comments, "--\n-- Table: $table_name\n--"
257 8 50       34 unless $options->{no_comments};
258 8         43 foreach my $field ($table->get_fields) {
259 36         113 push @field_defs, create_field($field);
260             }
261 8         23 my (@con_defs, @fks);
262 8         41 foreach my $con ($table->get_constraints) {
263 15         100 my ($cdefs, $fks) = create_constraint($con);
264 15         37 push @con_defs, @$cdefs;
265 15         48 push @fks, @$fks;
266             }
267              
268 8   50     254 my $tablespace = $table->extra()->{'TABLESPACE'} || '';
269 8         26 my $table_def = "CREATE TABLE $table_name (\n";
270 8         22 $table_def .= join(",\n", map {" $_"} @field_defs, @con_defs);
  48         119  
271 8         22 $table_def .= "\n)";
272 8 50       28 $table_def .= $tablespace ? "IN $tablespace;" : ';';
273              
274 8         44 return $table_def, \@fks;
275             }
276              
277             sub create_field {
278 38     38 0 159 my ($field) = @_;
279              
280 38         1038 my $field_name = check_name($field->name, 'fields', 30);
281              
282             # use Data::Dumper;
283             # print Dumper(\%dt_translate);
284             # print $field->data_type, " ", $dt_translate{lc($field->data_type)}, "\n";
285 38   66     315 my $data_type = uc($dt_translate{ lc($field->data_type) } || $field->data_type);
286 38         1014 my $size = $field->size();
287              
288 38         468 my $field_def = "$field_name $data_type";
289 38 100       764 $field_def
290             .= $field->is_auto_increment
291             ? ' GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1)'
292             : '';
293 38 100       713 $field_def .= $data_type =~ /(CHAR|CLOB|NUMERIC|DECIMAL)/i ? "(${size})" : '';
294 38 100       931 $field_def .= !$field->is_nullable ? ' NOT NULL' : '';
295              
296             # $field_def .= $field->is_primary_key ? ' PRIMARY KEY':'';
297 38 100 33     2540 $field_def
    50          
    50          
    100          
298             .= !defined $field->default_value
299             ? ''
300             : $field->default_value =~ /current( |_)timestamp/i
301             || $field->default_value =~ /\Qnow()\E/i ? ' DEFAULT CURRENT TIMESTAMP'
302             : defined $field->default_value ? (
303             " DEFAULT "
304             . (
305             $data_type =~ /(INT|DOUBLE)/i
306             ? $field->default_value
307             : "'" . $field->default_value . "'"
308             )
309             )
310             : '';
311              
312 38         178 return $field_def;
313             }
314              
315             sub create_index {
316 5     5 0 24 my ($index) = @_;
317              
318 5 50       129 my $out = sprintf(
319             'CREATE %sINDEX %s ON %s ( %s );',
320             $index->type() =~ /^UNIQUE$/i ? 'UNIQUE' : '',
321             $index->name, $index->table->name, join(', ', $index->fields)
322             );
323              
324 5         29 return $out;
325             }
326              
327             sub create_constraint {
328 15     15 0 29 my ($constraint) = @_;
329              
330 15         26 my (@con_defs, @fks);
331              
332 15 50       310 my $ctype
    50          
    100          
    100          
333             = $constraint->type =~ /^PRIMARY(_|\s)KEY$/i ? 'PRIMARY KEY'
334             : $constraint->type =~ /^UNIQUE$/i ? 'UNIQUE'
335             : $constraint->type =~ /^CHECK_C$/i ? 'CHECK'
336             : $constraint->type =~ /^FOREIGN(_|\s)KEY$/i ? 'FOREIGN KEY'
337             : '';
338              
339 15 50       794 my $expr
340             = $constraint->type =~ /^CHECK_C$/i
341             ? $constraint->expression
342             : '';
343 15 100       574 my $ref
344             = $constraint->type =~ /^FOREIGN(_|\s)KEY$/i
345             ? ('REFERENCES ' . $constraint->reference_table . '(' . join(', ', $constraint->reference_fields) . ')')
346             : '';
347 15 50       590 my $update = $constraint->on_update ? $constraint->on_update : '';
348 15 50       308 my $delete = $constraint->on_delete ? $constraint->on_delete : '';
349              
350             my $out = join(' ',
351 15 100       333 grep {$_} $constraint->name ? ('CONSTRAINT ' . $constraint->name) : '',
  90 50       527  
352             $ctype,
353             '(' . join(', ', $constraint->fields) . ')',
354             $expr ? $expr : $ref,
355             $update, $delete);
356              
357 15 100       358 if ($constraint->type eq FOREIGN_KEY) {
358 3         107 my $table_name = $constraint->table->name;
359 3         60 $out = "ALTER TABLE $table_name ADD $out;";
360 3         9 push @fks, $out;
361             } else {
362 12         286 push @con_defs, $out;
363             }
364              
365 15         63 return \@con_defs, \@fks;
366              
367             }
368              
369             sub create_view {
370 3     3 0 10 my ($view) = @_;
371              
372 3         48 my $out = sprintf("CREATE VIEW %s AS\n%s;", $view->name, $view->sql);
373              
374 3         14 return $out;
375             }
376              
377             sub create_trigger {
378 7     7 0 17 my ($trigger) = @_;
379              
380             # create: CREATE TRIGGER trigger_name before type /ON/i table_name reference_b(?) /FOR EACH ROW/i 'MODE DB2SQL' triggered_action
381              
382 7         174 my $db_events = join ', ', $trigger->database_events;
383             my $out = sprintf(
384             'CREATE TRIGGER %s %s %s ON %s %s %s MODE DB2SQL %s',
385             $trigger->name,
386             $trigger->perform_action_when || 'AFTER',
387             $db_events =~ /update_on/i
388             ? ('UPDATE OF ' . join(', ', $trigger->fields))
389             : $db_events || 'UPDATE',
390             $trigger->table->name,
391             $trigger->extra->{reference} || 'REFERENCING OLD AS oldrow NEW AS newrow',
392 7 50 50     242 $trigger->extra->{granularity} || 'FOR EACH ROW',
      50        
      50        
      50        
393             $trigger->action
394             );
395              
396 7         61 return $out;
397              
398             }
399              
400             sub alter_field {
401 1     1 0 93 my ($from_field, $to_field) = @_;
402              
403 1   33     14 my $data_type = uc($dt_translate{ lc($to_field->data_type) } || $to_field->data_type);
404              
405 1         35 my $size = $to_field->size();
406 1 50       19 $data_type .= $data_type =~ /CHAR/i ? "(${size})" : '';
407              
408             # DB2 will only allow changing of varchar/vargraphic datatypes
409             # to extend their lengths. Or changing of text types to other
410             # texttypes, and numeric types to larger numeric types. (v8)
411             # We can also drop/add keys, checks and constraints, but not
412             # columns !?
413              
414 1         29 my $out = sprintf('ALTER TABLE %s ALTER %s SET DATATYPE %s', $to_field->table->name, $to_field->name, $data_type);
415              
416             }
417              
418             sub add_field {
419 1     1 0 1028 my ($new_field) = @_;
420              
421 1         59 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s', $new_field->table->name, create_field($new_field));
422              
423 1         6 return $out;
424             }
425              
426             sub drop_field {
427 1     1 0 1003 my ($field) = @_;
428              
429 1         4 return '';
430             }
431             1;