Fixed "parse_list_args" to not stringify reference arguments.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / PostgreSQL.pm
CommitLineData
f8f0253c 1package SQL::Translator::Producer::PostgreSQL;
2
3# -------------------------------------------------------------------
4328d7bd 4# $Id: PostgreSQL.pm,v 1.16 2003-09-04 15:33:24 kycl4rk Exp $
f8f0253c 5# -------------------------------------------------------------------
abfa405a 6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
f8f0253c 9#
10# This program is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public License as
12# published by the Free Software Foundation; version 2.
13#
14# This program is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17# General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22# 02111-1307 USA
23# -------------------------------------------------------------------
24
96844cae 25=head1 NAME
26
27SQL::Translator::Producer::PostgreSQL - PostgreSQL producer for SQL::Translator
28
29=cut
30
f8f0253c 31use strict;
96844cae 32use vars qw[ $DEBUG $WARN $VERSION ];
4328d7bd 33$VERSION = sprintf "%d.%02d", q$Revision: 1.16 $ =~ /(\d+)\.(\d+)/;
f8f0253c 34$DEBUG = 1 unless defined $DEBUG;
35
0c43e0a1 36use SQL::Translator::Schema::Constants;
5ee19df8 37use SQL::Translator::Utils qw(header_comment);
f8f0253c 38use Data::Dumper;
39
d529894e 40my %translate = (
41 #
42 # MySQL types
43 #
44 bigint => 'bigint',
4328d7bd 45 double => 'numeric',
46 decimal => 'numeric',
47 float => 'numeric',
d529894e 48 int => 'integer',
49 mediumint => 'integer',
50 smallint => 'smallint',
51 tinyint => 'smallint',
52 char => 'char',
da8e499e 53 varchar => 'character varying',
d529894e 54 longtext => 'text',
55 mediumtext => 'text',
56 text => 'text',
57 tinytext => 'text',
58 tinyblob => 'bytea',
59 blob => 'bytea',
60 mediumblob => 'bytea',
61 longblob => 'bytea',
da8e499e 62 enum => 'character varying',
63 set => 'character varying',
d529894e 64 date => 'date',
65 datetime => 'timestamp',
66 time => 'date',
67 timestamp => 'timestamp',
68 year => 'date',
69
70 #
71 # Oracle types
72 #
96844cae 73 number => 'integer',
74 char => 'char',
da8e499e 75 varchar2 => 'character varying',
96844cae 76 long => 'text',
77 CLOB => 'bytea',
78 date => 'date',
79
80 #
81 # Sybase types
82 #
83 int => 'integer',
84 money => 'money',
da8e499e 85 varchar => 'character varying',
96844cae 86 datetime => 'timestamp',
87 text => 'text',
4328d7bd 88 real => 'numeric',
96844cae 89 comment => 'text',
90 bit => 'bit',
91 tinyint => 'smallint',
4328d7bd 92 float => 'numeric',
d529894e 93);
94
96844cae 95my %reserved = map { $_, 1 } qw[
96 ALL ANALYSE ANALYZE AND ANY AS ASC
97 BETWEEN BINARY BOTH
98 CASE CAST CHECK COLLATE COLUMN CONSTRAINT CROSS
99 CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
100 DEFAULT DEFERRABLE DESC DISTINCT DO
101 ELSE END EXCEPT
102 FALSE FOR FOREIGN FREEZE FROM FULL
103 GROUP HAVING
104 ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL
105 JOIN LEADING LEFT LIKE LIMIT
106 NATURAL NEW NOT NOTNULL NULL
107 OFF OFFSET OLD ON ONLY OR ORDER OUTER OVERLAPS
108 PRIMARY PUBLIC REFERENCES RIGHT
109 SELECT SESSION_USER SOME TABLE THEN TO TRAILING TRUE
110 UNION UNIQUE USER USING VERBOSE WHEN WHERE
111];
d529894e 112
96844cae 113my $max_id_length = 30;
114my %used_identifiers = ();
115my %global_names;
116my %unreserve;
117my %truncated;
118
119=pod
120
121=head1 PostgreSQL Create Table Syntax
122
123 CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
124 { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
125 | table_constraint } [, ... ]
126 )
127 [ INHERITS ( parent_table [, ... ] ) ]
128 [ WITH OIDS | WITHOUT OIDS ]
129
130where column_constraint is:
131
132 [ CONSTRAINT constraint_name ]
133 { NOT NULL | NULL | UNIQUE | PRIMARY KEY |
134 CHECK (expression) |
135 REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
136 [ ON DELETE action ] [ ON UPDATE action ] }
137 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
138
139and table_constraint is:
140
141 [ CONSTRAINT constraint_name ]
142 { UNIQUE ( column_name [, ... ] ) |
143 PRIMARY KEY ( column_name [, ... ] ) |
144 CHECK ( expression ) |
145 FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
146 [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
147 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
148
da8e499e 149=head1 Create Index Syntax
150
151 CREATE [ UNIQUE ] INDEX index_name ON table
152 [ USING acc_method ] ( column [ ops_name ] [, ...] )
153 [ WHERE predicate ]
154 CREATE [ UNIQUE ] INDEX index_name ON table
155 [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
156 [ WHERE predicate ]
157
96844cae 158=cut
f8f0253c 159
96844cae 160# -------------------------------------------------------------------
f8f0253c 161sub produce {
a1d94525 162 my $translator = shift;
163 $DEBUG = $translator->debug;
164 $WARN = $translator->show_warnings;
165 my $no_comments = $translator->no_comments;
166 my $add_drop_table = $translator->add_drop_table;
167 my $schema = $translator->schema;
96844cae 168
da8e499e 169 my $output;
5ee19df8 170 $output .= header_comment unless ($no_comments);
50840472 171 my %used_index_names;
96844cae 172
0c43e0a1 173 for my $table ( $schema->get_tables ) {
174 my $table_name = $table->name or next;
da8e499e 175 $table_name = mk_name( $table_name, '', undef, 1 );
176 my $table_name_ur = unreserve($table_name);
177
0c43e0a1 178 my ( @comments, @field_defs, @sequence_defs, @constraint_defs );
96844cae 179
da8e499e 180 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
f8f0253c 181
182 #
183 # Fields
184 #
96844cae 185 my %field_name_scope;
0c43e0a1 186 for my $field ( $table->get_fields ) {
96844cae 187 my $field_name = mk_name(
0c43e0a1 188 $field->name, '', \%field_name_scope, 1
96844cae 189 );
190 my $field_name_ur = unreserve( $field_name, $table_name );
0c43e0a1 191 my $field_def = qq["$field_name_ur"];
da8e499e 192
193 #
194 # Datatype
195 #
0c43e0a1 196 my @size = $field->size;
197 my $data_type = lc $field->data_type;
198 my %extra = $field->extra;
199 my $list = $extra{'list'} || [];
4524cf01 200 # todo deal with embedded quotes
201 my $commalist = join( ', ', map { qq['$_'] } @$list );
da8e499e 202 my $seq_name;
203
204 if ( $data_type eq 'enum' ) {
205 my $len = 0;
206 $len = ($len < length($_)) ? length($_) : $len for (@$list);
4524cf01 207 my $chk_name = mk_name( $table_name.'_'.$field_name, 'chk' );
0c43e0a1 208 push @constraint_defs,
4328d7bd 209 qq[Constraint "$chk_name" CHECK ("$field_name" ].
210 qq[IN ($commalist))];
0c43e0a1 211 $data_type = 'character varying';
da8e499e 212 }
213 elsif ( $data_type eq 'set' ) {
214 # XXX add a CHECK constraint maybe
215 # (trickier and slower, than enum :)
0c43e0a1 216# my $len = length $commalist;
217# $field_def .= " character varying($len) /* set $commalist */";
218 $data_type = 'character varying';
da8e499e 219 }
0c43e0a1 220 elsif ( $field->is_auto_increment ) {
50840472 221 if ( defined $size[0] && $size[0] > 11 ) {
4328d7bd 222 $data_type = 'bigserial';
50840472 223 }
224 else {
4328d7bd 225 $data_type = 'serial';
50840472 226 }
227 undef @size;
228
0c43e0a1 229# $seq_name = mk_name( $table_name.'_'.$field_name, 'sq' );
230# push @sequence_defs, qq[DROP SEQUENCE "$seq_name";];
231# push @sequence_defs, qq[CREATE SEQUENCE "$seq_name";];
da8e499e 232 }
233 else {
234 $data_type = defined $translate{ $data_type } ?
235 $translate{ $data_type } :
61aa0c9d 236 $data_type;
0c43e0a1 237 }
238
50840472 239 if ( $data_type =~ /timestamp/i ) {
240 if ( defined $size[0] && $size[0] > 13 ) {
241 $size[0] = 13;
242 }
243 }
244
245 if ( $data_type eq 'integer' ) {
246 if ( defined $size[0] ) {
4328d7bd 247 if ( $size[0] > 10 ) {
248 $data_type = 'bigint';
50840472 249 }
250 elsif ( $size[0] < 5 ) {
4328d7bd 251 $data_type = 'smallint';
50840472 252 }
253 else {
4328d7bd 254 $data_type = 'integer';
50840472 255 }
256 }
257 else {
4328d7bd 258 $data_type = 'integer';
50840472 259 }
50840472 260 }
4328d7bd 261
262 #
263 # PG doesn't need a size for integers or text
264 #
265 undef @size if $data_type =~ m/(integer|smallint|bigint|text)/;
50840472 266
0c43e0a1 267 $field_def .= " $data_type";
268
269 if ( defined $size[0] && $size[0] > 0 ) {
4328d7bd 270 $field_def .= '(' . join( ',', @size ) . ')';
f8f0253c 271 }
272
da8e499e 273 #
274 # Default value
275 #
0c43e0a1 276 my $default = $field->default_value;
277 if ( defined $default ) {
278 $field_def .= sprintf( ' DEFAULT %s',
279 ( $field->is_auto_increment && $seq_name )
da8e499e 280 ? qq[nextval('"$seq_name"'::text)] :
0c43e0a1 281 ( $default =~ m/null/i ) ? 'NULL' : "'$default'"
da8e499e 282 );
283 }
f8f0253c 284
da8e499e 285 #
286 # Not null constraint
287 #
0c43e0a1 288 $field_def .= ' NOT NULL' unless $field->is_nullable;
f8f0253c 289
0c43e0a1 290 push @field_defs, $field_def;
f8f0253c 291 }
f8f0253c 292
293 #
da8e499e 294 # Index Declarations
f8f0253c 295 #
0c43e0a1 296 my @index_defs = ();
da8e499e 297 my $idx_name_default;
0c43e0a1 298 for my $index ( $table->get_indices ) {
299 my $name = $index->name || '';
300 my $type = $index->type || NORMAL;
d8dcdb7a 301 my @fields =
302 map { $_ =~ s/\(.+\)//; $_ }
303 map { unreserve( $_, $table_name ) }
0c43e0a1 304 $index->fields;
da8e499e 305 next unless @fields;
306
0c43e0a1 307 if ( $type eq PRIMARY_KEY ) {
308 $name ||= mk_name( $table_name, 'pk' );
50840472 309 $name = next_unused_name($name, \%used_index_names);
310 # how do I get next_unused_name() to do: ?
311 $used_index_names{$name} = $name;
4328d7bd 312 push @constraint_defs, 'Constraint "'.$name.'" PRIMARY KEY '.
50840472 313 '("' . join( '", "', @fields ) . '")';
da8e499e 314 }
0c43e0a1 315 elsif ( $type eq UNIQUE ) {
316 $name ||= mk_name(
317 $table_name, $name || ++$idx_name_default
da8e499e 318 );
50840472 319 $name = next_unused_name($name, \%used_index_names);
320 $used_index_names{$name} = $name;
4328d7bd 321 push @constraint_defs, 'Constraint "' . $name . '" UNIQUE ' .
50840472 322 '("' . join( '", "', @fields ) . '")';
da8e499e 323 }
0c43e0a1 324 elsif ( $type eq NORMAL ) {
325 $name ||= mk_name(
326 $table_name, $name || ++$idx_name_default
da8e499e 327 );
50840472 328 $name = next_unused_name($name, \%used_index_names);
329 $used_index_names{$name} = $name;
0c43e0a1 330 push @index_defs,
50840472 331 qq[CREATE INDEX "$name" on $table_name_ur ("].
332 join( '", "', @fields ).
333 '");';
da8e499e 334 }
f8f0253c 335 else {
0c43e0a1 336 warn "Unknown index type ($type) on table $table_name.\n"
da8e499e 337 if $WARN;
f8f0253c 338 }
339 }
340
0c43e0a1 341 #
342 # Table constraints
343 #
344 my $c_name_default;
345 for my $c ( $table->get_constraints ) {
346 my $name = $c->name || '';
347 my @fields =
348 map { $_ =~ s/\(.+\)//; $_ }
349 map { unreserve( $_, $table_name ) }
350 $c->fields;
351 my @rfields =
352 map { $_ =~ s/\(.+\)//; $_ }
353 map { unreserve( $_, $table_name ) }
354 $c->reference_fields;
8065e024 355 next if !@fields && $c->type ne CHECK_C;
0c43e0a1 356
357 if ( $c->type eq PRIMARY_KEY ) {
358 $name ||= mk_name( $table_name, 'pk' );
50840472 359 $name = next_unused_name($name, \%used_index_names);
360 $used_index_names{$name} = $name;
4328d7bd 361 push @constraint_defs, qq[Constraint "$name" PRIMARY KEY ].
50840472 362 '("' . join( '", "', @fields ) . '")';
0c43e0a1 363 }
364 elsif ( $c->type eq UNIQUE ) {
365 $name ||= mk_name(
366 $table_name, $name || ++$c_name_default
367 );
50840472 368 $name = next_unused_name($name, \%used_index_names);
369 $used_index_names{$name} = $name;
4328d7bd 370 push @constraint_defs, qq[Constraint "$name" UNIQUE ] .
50840472 371 '("' . join( '", "', @fields ) . '")';
0c43e0a1 372 }
8065e024 373 elsif ( $c->type eq CHECK_C ) {
374 my $s;
375 if ( $name ) {
376 $name ||= mk_name(
377 $table_name, $name || ++$c_name_default
378 );
379 $name = next_unused_name($name, \%used_index_names);
380 $used_index_names{$name} = $name;
4328d7bd 381 $s = 'Constraint "$name" ';
8065e024 382 }
383 my $expression = $c->expression;
384 push @constraint_defs, "${s}CHECK ($expression)";
385 }
0c43e0a1 386 elsif ( $c->type eq FOREIGN_KEY ) {
387 my $def = join(' ',
388 map { $_ || () } 'FOREIGN KEY', $c->name
389 );
390
50840472 391 $def .= ' ("' . join( '", "', @fields ) . '")';
0c43e0a1 392
393 $def .= ' REFERENCES ' . $c->reference_table;
394
395 if ( @rfields ) {
50840472 396 $def .= ' ("' . join( '", "', @rfields ) . '")';
0c43e0a1 397 }
398
399 if ( $c->match_type ) {
400 $def .= ' MATCH ' .
401 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
402 }
403
404 if ( $c->on_delete ) {
405 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
406 }
407
408 if ( $c->on_update ) {
409 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
410 }
411
412 push @constraint_defs, $def;
413 }
414 }
415
da8e499e 416 my $create_statement;
417 $create_statement = qq[DROP TABLE "$table_name_ur";\n]
418 if $add_drop_table;
419 $create_statement .= qq[CREATE TABLE "$table_name_ur" (\n].
0c43e0a1 420 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
da8e499e 421 "\n);"
422 ;
423
424 $output .= join( "\n\n",
425 @comments,
0c43e0a1 426 @sequence_defs,
da8e499e 427 $create_statement,
0c43e0a1 428 @index_defs,
da8e499e 429 ''
430 );
431 }
432
433 if ( $WARN ) {
434 if ( %truncated ) {
435 warn "Truncated " . keys( %truncated ) . " names:\n";
436 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
437 }
438
439 if ( %unreserve ) {
440 warn "Encounted " . keys( %unreserve ) .
441 " unsafe names in schema (reserved or invalid):\n";
442 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
443 }
f8f0253c 444 }
445
da8e499e 446 return $output;
f8f0253c 447}
448
96844cae 449# -------------------------------------------------------------------
450sub mk_name {
0c43e0a1 451 my $basename = shift || '';
452 my $type = shift || '';
453 my $scope = shift || '';
454 my $critical = shift || '';
96844cae 455 my $basename_orig = $basename;
2ad4c2c8 456 my $max_name = $type
457 ? $max_id_length - (length($type) + 1)
458 : $max_id_length;
96844cae 459 $basename = substr( $basename, 0, $max_name )
460 if length( $basename ) > $max_name;
461 my $name = $type ? "${type}_$basename" : $basename;
462
463 if ( $basename ne $basename_orig and $critical ) {
464 my $show_type = $type ? "+'$type'" : "";
465 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
466 "character limit to make '$name'\n" if $WARN;
467 $truncated{ $basename_orig } = $name;
468 }
469
470 $scope ||= \%global_names;
471 if ( my $prev = $scope->{ $name } ) {
472 my $name_orig = $name;
473 $name .= sprintf( "%02d", ++$prev );
474 substr($name, $max_id_length - 3) = "00"
475 if length( $name ) > $max_id_length;
476
477 warn "The name '$name_orig' has been changed to ",
478 "'$name' to make it unique.\n" if $WARN;
479
480 $scope->{ $name_orig }++;
f8f0253c 481 }
96844cae 482
483 $scope->{ $name }++;
484 return $name;
485}
486
487# -------------------------------------------------------------------
488sub unreserve {
0c43e0a1 489 my $name = shift || '';
490 my $schema_obj_name = shift || '';
491
96844cae 492 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
493
494 # also trap fields that don't begin with a letter
0c43e0a1 495 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
96844cae 496
497 if ( $schema_obj_name ) {
498 ++$unreserve{"$schema_obj_name.$name"};
499 }
500 else {
501 ++$unreserve{"$name (table name)"};
502 }
503
504 my $unreserve = sprintf '%s_', $name;
505 return $unreserve.$suffix;
f8f0253c 506}
507
50840472 508# -------------------------------------------------------------------
509sub next_unused_name {
510 my $name = shift || '';
511 my $used_names = shift || '';
512
513 my %used_names = %$used_names;
514
515 if ( !defined($used_names{$name}) ) {
516 $used_names{$name} = $name;
517 return $name;
518 }
519
520 my $i = 2;
521 while ( defined($used_names{$name . $i}) ) {
522 ++$i;
523 }
524 $name .= $i;
525 $used_names{$name} = $name;
526 return $name;
527}
528
f8f0253c 5291;
f8f0253c 530
96844cae 531# -------------------------------------------------------------------
532# Life is full of misery, loneliness, and suffering --
533# and it's all over much too soon.
534# Woody Allen
535# -------------------------------------------------------------------
f8f0253c 536
96844cae 537=pod
f8f0253c 538
539=head1 AUTHOR
540
541Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
542
543=cut