added a serial->int auto_increment fix, a varchar->varchar(255) workaround.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / PostgreSQL.pm
CommitLineData
84012a55 1package SQL::Translator::Parser::PostgreSQL;
4422e22a 2
3# -------------------------------------------------------------------
ba1a1626 4# $Id: PostgreSQL.pm,v 1.2 2003-02-25 01:01:29 allenday Exp $
4422e22a 5# -------------------------------------------------------------------
6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
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
25=head1 NAME
26
84012a55 27SQL::Translator::Parser::PostgreSQL - parser for PostgreSQL
4422e22a 28
29=head1 SYNOPSIS
30
31 use SQL::Translator;
84012a55 32 use SQL::Translator::Parser::PostgreSQL;
4422e22a 33
34 my $translator = SQL::Translator->new;
84012a55 35 $translator->parser("SQL::Translator::Parser::PostgreSQL");
4422e22a 36
37=head1 DESCRIPTION
38
39The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
40
41=cut
42
43use strict;
44use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
ba1a1626 45$VERSION = sprintf "%d.%02d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/;
4422e22a 46$DEBUG = 0 unless defined $DEBUG;
47
48use Data::Dumper;
49use Parse::RecDescent;
50use Exporter;
51use base qw(Exporter);
52
53@EXPORT_OK = qw(parse);
54
55# Enable warnings within the Parse::RecDescent module.
56$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
57$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
58$::RD_HINT = 1; # Give out hints to help fix problems.
59
60my $parser; # should we do this? There's no programmic way to
61 # change the grammar, so I think this is safe.
62
63$GRAMMAR = q!
64
65{ our ( %tables, $table_order ) }
66
67startrule : statement(s) { \%tables }
68
69statement : comment
70 | drop
71 | create
72 | <error>
73
74drop : /drop/i WORD(s) ';'
75
ba1a1626 76create : create_table table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
4422e22a 77 {
78 my $table_name = $item{'table_name'};
79 $tables{ $table_name }{'order'} = ++$table_order;
80 $tables{ $table_name }{'table_name'} = $table_name;
81
82 my $i = 1;
83 for my $definition ( @{ $item[4] } ) {
84 if ( $definition->{'type'} eq 'field' ) {
85 my $field_name = $definition->{'name'};
86 $tables{ $table_name }{'fields'}{ $field_name } =
87 { %$definition, order => $i };
88 $i++;
89
90 if ( $definition->{'is_primary_key'} ) {
91 push @{ $tables{ $table_name }{'indices'} },
92 {
93 type => 'primary_key',
94 fields => [ $field_name ],
95 }
96 ;
97 }
98 }
99 else {
100 push @{ $tables{ $table_name }{'indices'} },
101 $definition;
102 }
103 }
104
105 for my $opt ( @{ $item{'table_option'} } ) {
106 if ( my ( $key, $val ) = each %$opt ) {
107 $tables{ $table_name }{'table_options'}{ $key } = $val;
108 }
109 }
110 }
111
112 create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
113 {
114 push @{ $tables{ $item{'table_name'} }{'indices'} },
115 {
116 name => $item[4],
117 type => $item[2] ? 'unique' : 'normal',
118 fields => $item[8],
119 }
120 ;
121 }
122
123create_definition : index
124 | field
125 | <error>
126
127comment : /^\s*(?:#|-{2}).*\n/
128
129blank : /\s*/
130
131field : field_name data_type field_qualifier(s?)
132 {
133 my %qualifiers = map { %$_ } @{ $item{'field_qualifier'} || [] };
134 my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
135 delete $qualifiers{'not_null'};
136 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
137 $qualifiers{ $_ } = 1 for @type_quals;
138 }
139
140 $return = {
141 type => 'field',
142 name => $item{'field_name'},
143 data_type => $item{'data_type'}{'type'},
144 size => $item{'data_type'}{'size'},
145 list => $item{'data_type'}{'list'},
146 null => $null,
147 %qualifiers,
148 }
149 }
150 | <error>
151
152field_qualifier : not_null
153 {
154 $return = {
155 null => $item{'not_null'},
156 }
157 }
158
159field_qualifier : default_val
160 {
161 $return = {
162 default => $item{'default_val'},
163 }
164 }
165
166field_qualifier : auto_inc
167 {
168 $return = {
169 is_auto_inc => $item{'auto_inc'},
170 }
171 }
172
173field_qualifier : ',' primary_key '(' field_name ')'
174 {
175 $return = {
176 is_primary_key => $item{'primary_key'},
177 }
178 }
179
180field_qualifier : ',' foreign_key '(' field_name ')' foreign_key_reference foreign_table_name '(' foreign_field_name ')'
181 {
182 $return = {
183 is_foreign_key => $item{'foreign_key'},
184 foreign_table => $item{'foreign_table_name'},
185 foreign_field => $item{'foreign_field_name'},
186 name => $item{'field_name'},
ba1a1626 187 }
4422e22a 188 }
189
190field_qualifier : unsigned
191 {
192 $return = {
193 is_unsigned => $item{'unsigned'},
194 }
195 }
196
197index : primary_key_index
198 | unique_index
199 | fulltext_index
200 | normal_index
201
202table_name : WORD
203
204foreign_table_name : WORD
205
206field_name : WORD
207
208foreign_field_name : WORD
209
210index_name : WORD
211
212data_type : WORD parens_value_list(s?) type_qualifier(s?)
213 {
214 my $type = $item[1];
ba1a1626 215
4422e22a 216 my $size; # field size, applicable only to non-set fields
217 my $list; # set list, applicable only to sets (duh)
218
ba1a1626 219
220 if(uc($type) =~ /^SERIAL$/){
221 $type = 'int';
222 } elsif ( uc($type) =~ /^(SET|ENUM)$/ ) {
4422e22a 223 $size = undef;
224 $list = $item[2][0];
225 }
226 else {
227 $size = $item[2][0];
228 $list = [];
229 }
230
ba1a1626 231 if(uc($type) =~ /^VARCHAR$/ and not defined($size)){ $size = [255] }
232
4422e22a 233 $return = {
234 type => $type,
235 size => $size,
236 list => $list,
237 qualifiers => $item[3],
238 }
239 }
240
241parens_value_list : '(' VALUE(s /,/) ')'
242 { $item[2] }
243
244type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
245 { lc $item[1] }
246
247field_type : WORD
248
249field_size : '(' num_range ')' { $item{'num_range'} }
250
251num_range : DIGITS ',' DIGITS
252 { $return = $item[1].','.$item[3] }
253 | DIGITS
254 { $return = $item[1] }
255
256create_table : /create/i /table/i
257
258create_index : /create/i /index/i
259
260not_null : /not/i /null/i { $return = 0 }
261
262unsigned : /unsigned/i { $return = 0 }
263
264default_val : /default/i /(?:')?[\w\d.-]*(?:')?/
265 {
266 $item[2] =~ s/'//g;
267 $return = $item[2];
268 }
269
ba1a1626 270auto_inc : /auto_increment/i { 1 } #see data_type
4422e22a 271
272primary_key : /primary/i /key/i { 1 }
273
274foreign_key : /foreign/i /key/i { 1 }
275
276foreign_key_reference : /references/i { 1 }
277
278primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
279 {
280 $return = {
281 name => $item{'index_name'}[0],
282 type => 'primary_key',
283 fields => $item[4],
284 }
285 }
286
287normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
288 {
289 $return = {
290 name => $item{'index_name'}[0],
291 type => 'normal',
292 fields => $item[4],
293 }
294 }
295
296unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
297 {
298 $return = {
299 name => $item{'index_name'}[0],
300 type => 'unique',
301 fields => $item[5],
302 }
303 }
304
305fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
306 {
307 $return = {
308 name => $item{'index_name'}[0],
309 type => 'fulltext',
310 fields => $item[5],
311 }
312 }
313
314name_with_opt_paren : NAME parens_value_list(s?)
315 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
316
317fulltext : /fulltext/i { 1 }
318
319unique : /unique/i { 1 }
320
321key : /key/i | /index/i
322
323table_option : /[^\s;]*/
324 {
325 $return = { split /=/, $item[1] }
326 }
327
328WORD : /\w+/
329
330DIGITS : /\d+/
331
332COMMA : ','
333
334NAME : "`" /\w+/ "`"
335 { $item[2] }
336 | /\w+/
337 { $item[1] }
338
339VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
340 { $item[1] }
341 | /'.*?'/ # XXX doesn't handle embedded quotes
342 { $item[1] }
343 | /NULL/
344 { 'NULL' }
345
346!;
347
348# -------------------------------------------------------------------
349sub parse {
350 my ( $translator, $data ) = @_;
351 $parser ||= Parse::RecDescent->new($GRAMMAR);
352
353 $::RD_TRACE = $translator->trace ? 1 : undef;
354 $DEBUG = $translator->debug;
355
356 unless (defined $parser) {
357 return $translator->error("Error instantiating Parse::RecDescent ".
358 "instance: Bad grammer");
359 }
360
361 my $result = $parser->startrule($data);
362 die "Parse failed.\n" unless defined $result;
363 warn Dumper($result) if $DEBUG;
364 return $result;
365}
366
3671;
368
369#-----------------------------------------------------
370# Where man is not nature is barren.
371# William Blake
372#-----------------------------------------------------
373
374=pod
375
376=head1 AUTHOR
377
378Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
379Chris Mungall
380
381=head1 SEE ALSO
382
383perl(1), Parse::RecDescent.
384
385=cut