We need to quote the enum field values
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / PostgreSQL.pm
CommitLineData
f8f0253c 1package SQL::Translator::Producer::PostgreSQL;
2
3# -------------------------------------------------------------------
d9397268 4# $Id: PostgreSQL.pm,v 1.12 2003-08-16 20:12:09 rossta 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 ];
d9397268 33$VERSION = sprintf "%d.%02d", q$Revision: 1.12 $ =~ /(\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',
45 double => 'double precision',
46 decimal => 'decimal',
47 float => 'double precision',
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',
88 real => 'double precision',
89 comment => 'text',
90 bit => 'bit',
91 tinyint => 'smallint',
92 float => 'double precision',
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);
96844cae 171
0c43e0a1 172 for my $table ( $schema->get_tables ) {
173 my $table_name = $table->name or next;
da8e499e 174 $table_name = mk_name( $table_name, '', undef, 1 );
175 my $table_name_ur = unreserve($table_name);
176
0c43e0a1 177 my ( @comments, @field_defs, @sequence_defs, @constraint_defs );
96844cae 178
da8e499e 179 push @comments, "--\n-- Table: $table_name_ur\n--" unless $no_comments;
f8f0253c 180
181 #
182 # Fields
183 #
96844cae 184 my %field_name_scope;
0c43e0a1 185 for my $field ( $table->get_fields ) {
96844cae 186 my $field_name = mk_name(
0c43e0a1 187 $field->name, '', \%field_name_scope, 1
96844cae 188 );
189 my $field_name_ur = unreserve( $field_name, $table_name );
0c43e0a1 190 my $field_def = qq["$field_name_ur"];
da8e499e 191
192 #
193 # Datatype
194 #
0c43e0a1 195 my @size = $field->size;
196 my $data_type = lc $field->data_type;
197 my %extra = $field->extra;
198 my $list = $extra{'list'} || [];
d9397268 199 # \todo deal with embedded quotes
200 my $commalist = "'" . join("','", @$list) . "'";
da8e499e 201 my $seq_name;
202
203 if ( $data_type eq 'enum' ) {
204 my $len = 0;
205 $len = ($len < length($_)) ? length($_) : $len for (@$list);
206 my $check_name = mk_name( $table_name.'_'.$field_name, 'chk' );
0c43e0a1 207 push @constraint_defs,
da8e499e 208 "CONSTRAINT $check_name CHECK ($field_name IN ($commalist))";
0c43e0a1 209 $data_type = 'character varying';
da8e499e 210 }
211 elsif ( $data_type eq 'set' ) {
212 # XXX add a CHECK constraint maybe
213 # (trickier and slower, than enum :)
0c43e0a1 214# my $len = length $commalist;
215# $field_def .= " character varying($len) /* set $commalist */";
216 $data_type = 'character varying';
da8e499e 217 }
0c43e0a1 218 elsif ( $field->is_auto_increment ) {
219 $field_def .= ' serial';
220# $seq_name = mk_name( $table_name.'_'.$field_name, 'sq' );
221# push @sequence_defs, qq[DROP SEQUENCE "$seq_name";];
222# push @sequence_defs, qq[CREATE SEQUENCE "$seq_name";];
da8e499e 223 }
224 else {
225 $data_type = defined $translate{ $data_type } ?
226 $translate{ $data_type } :
61aa0c9d 227 $data_type;
0c43e0a1 228 }
229
230 $field_def .= " $data_type";
231
232 if ( defined $size[0] && $size[0] > 0 ) {
233 $field_def .= '(' . join( ', ', @size ) . ')';
f8f0253c 234 }
235
da8e499e 236 #
237 # Default value
238 #
0c43e0a1 239 my $default = $field->default_value;
240 if ( defined $default ) {
241 $field_def .= sprintf( ' DEFAULT %s',
242 ( $field->is_auto_increment && $seq_name )
da8e499e 243 ? qq[nextval('"$seq_name"'::text)] :
0c43e0a1 244 ( $default =~ m/null/i ) ? 'NULL' : "'$default'"
da8e499e 245 );
246 }
f8f0253c 247
da8e499e 248 #
249 # Not null constraint
250 #
0c43e0a1 251 $field_def .= ' NOT NULL' unless $field->is_nullable;
f8f0253c 252
0c43e0a1 253 push @field_defs, $field_def;
f8f0253c 254 }
f8f0253c 255
256 #
da8e499e 257 # Index Declarations
f8f0253c 258 #
0c43e0a1 259 my @index_defs = ();
da8e499e 260 my $idx_name_default;
0c43e0a1 261 for my $index ( $table->get_indices ) {
262 my $name = $index->name || '';
263 my $type = $index->type || NORMAL;
d8dcdb7a 264 my @fields =
265 map { $_ =~ s/\(.+\)//; $_ }
266 map { unreserve( $_, $table_name ) }
0c43e0a1 267 $index->fields;
da8e499e 268 next unless @fields;
269
0c43e0a1 270 if ( $type eq PRIMARY_KEY ) {
271 $name ||= mk_name( $table_name, 'pk' );
272 push @constraint_defs, 'CONSTRAINT '.$name.' PRIMARY KEY '.
da8e499e 273 '(' . join( ', ', @fields ) . ')';
274 }
0c43e0a1 275 elsif ( $type eq UNIQUE ) {
276 $name ||= mk_name(
277 $table_name, $name || ++$idx_name_default
da8e499e 278 );
0c43e0a1 279 push @constraint_defs, 'CONSTRAINT ' . $name . ' UNIQUE ' .
da8e499e 280 '(' . join( ', ', @fields ) . ')';
281 }
0c43e0a1 282 elsif ( $type eq NORMAL ) {
283 $name ||= mk_name(
284 $table_name, $name || ++$idx_name_default
da8e499e 285 );
0c43e0a1 286 push @index_defs,
287 qq[CREATE INDEX "$name" on $table_name_ur (].
da8e499e 288 join( ', ', @fields ).
289 ');';
290 }
f8f0253c 291 else {
0c43e0a1 292 warn "Unknown index type ($type) on table $table_name.\n"
da8e499e 293 if $WARN;
f8f0253c 294 }
295 }
296
0c43e0a1 297 #
298 # Table constraints
299 #
300 my $c_name_default;
301 for my $c ( $table->get_constraints ) {
302 my $name = $c->name || '';
303 my @fields =
304 map { $_ =~ s/\(.+\)//; $_ }
305 map { unreserve( $_, $table_name ) }
306 $c->fields;
307 my @rfields =
308 map { $_ =~ s/\(.+\)//; $_ }
309 map { unreserve( $_, $table_name ) }
310 $c->reference_fields;
311 next unless @fields;
312
313 if ( $c->type eq PRIMARY_KEY ) {
314 $name ||= mk_name( $table_name, 'pk' );
315 push @constraint_defs, "CONSTRAINT $name PRIMARY KEY ".
316 '(' . join( ', ', @fields ) . ')';
317 }
318 elsif ( $c->type eq UNIQUE ) {
319 $name ||= mk_name(
320 $table_name, $name || ++$c_name_default
321 );
322 push @constraint_defs, "CONSTRAINT $name UNIQUE " .
323 '(' . join( ', ', @fields ) . ')';
324 }
325 elsif ( $c->type eq FOREIGN_KEY ) {
326 my $def = join(' ',
327 map { $_ || () } 'FOREIGN KEY', $c->name
328 );
329
330 $def .= ' (' . join( ', ', @fields ) . ')';
331
332 $def .= ' REFERENCES ' . $c->reference_table;
333
334 if ( @rfields ) {
335 $def .= ' (' . join( ', ', @rfields ) . ')';
336 }
337
338 if ( $c->match_type ) {
339 $def .= ' MATCH ' .
340 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
341 }
342
343 if ( $c->on_delete ) {
344 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
345 }
346
347 if ( $c->on_update ) {
348 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
349 }
350
351 push @constraint_defs, $def;
352 }
353 }
354
da8e499e 355 my $create_statement;
356 $create_statement = qq[DROP TABLE "$table_name_ur";\n]
357 if $add_drop_table;
358 $create_statement .= qq[CREATE TABLE "$table_name_ur" (\n].
0c43e0a1 359 join( ",\n", map { " $_" } @field_defs, @constraint_defs ).
da8e499e 360 "\n);"
361 ;
362
363 $output .= join( "\n\n",
364 @comments,
0c43e0a1 365 @sequence_defs,
da8e499e 366 $create_statement,
0c43e0a1 367 @index_defs,
da8e499e 368 ''
369 );
370 }
371
372 if ( $WARN ) {
373 if ( %truncated ) {
374 warn "Truncated " . keys( %truncated ) . " names:\n";
375 warn "\t" . join( "\n\t", sort keys %truncated ) . "\n";
376 }
377
378 if ( %unreserve ) {
379 warn "Encounted " . keys( %unreserve ) .
380 " unsafe names in schema (reserved or invalid):\n";
381 warn "\t" . join( "\n\t", sort keys %unreserve ) . "\n";
382 }
f8f0253c 383 }
384
da8e499e 385 return $output;
f8f0253c 386}
387
96844cae 388# -------------------------------------------------------------------
389sub mk_name {
0c43e0a1 390 my $basename = shift || '';
391 my $type = shift || '';
392 my $scope = shift || '';
393 my $critical = shift || '';
96844cae 394 my $basename_orig = $basename;
2ad4c2c8 395 my $max_name = $type
396 ? $max_id_length - (length($type) + 1)
397 : $max_id_length;
96844cae 398 $basename = substr( $basename, 0, $max_name )
399 if length( $basename ) > $max_name;
400 my $name = $type ? "${type}_$basename" : $basename;
401
402 if ( $basename ne $basename_orig and $critical ) {
403 my $show_type = $type ? "+'$type'" : "";
404 warn "Truncating '$basename_orig'$show_type to $max_id_length ",
405 "character limit to make '$name'\n" if $WARN;
406 $truncated{ $basename_orig } = $name;
407 }
408
409 $scope ||= \%global_names;
410 if ( my $prev = $scope->{ $name } ) {
411 my $name_orig = $name;
412 $name .= sprintf( "%02d", ++$prev );
413 substr($name, $max_id_length - 3) = "00"
414 if length( $name ) > $max_id_length;
415
416 warn "The name '$name_orig' has been changed to ",
417 "'$name' to make it unique.\n" if $WARN;
418
419 $scope->{ $name_orig }++;
f8f0253c 420 }
96844cae 421
422 $scope->{ $name }++;
423 return $name;
424}
425
426# -------------------------------------------------------------------
427sub unreserve {
0c43e0a1 428 my $name = shift || '';
429 my $schema_obj_name = shift || '';
430
96844cae 431 my ( $suffix ) = ( $name =~ s/(\W.*)$// ) ? $1 : '';
432
433 # also trap fields that don't begin with a letter
0c43e0a1 434 return $name if !$reserved{ uc $name } && $name =~ /^[a-z]/i;
96844cae 435
436 if ( $schema_obj_name ) {
437 ++$unreserve{"$schema_obj_name.$name"};
438 }
439 else {
440 ++$unreserve{"$name (table name)"};
441 }
442
443 my $unreserve = sprintf '%s_', $name;
444 return $unreserve.$suffix;
f8f0253c 445}
446
4471;
f8f0253c 448
96844cae 449# -------------------------------------------------------------------
450# Life is full of misery, loneliness, and suffering --
451# and it's all over much too soon.
452# Woody Allen
453# -------------------------------------------------------------------
f8f0253c 454
96844cae 455=pod
f8f0253c 456
457=head1 AUTHOR
458
459Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
460
461=cut