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