Changed grammar to bring it more inline with the official MySQL YACC
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
1 package SQL::Translator::Parser::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.19 2003-06-03 22:11:55 kycl4rk Exp $
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
27 SQL::Translator::Parser::MySQL - parser for MySQL
28
29 =head1 SYNOPSIS
30
31   use SQL::Translator;
32   use SQL::Translator::Parser::MySQL;
33
34   my $translator = SQL::Translator->new;
35   $translator->parser("SQL::Translator::Parser::MySQL");
36
37 =head1 DESCRIPTION
38
39 The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
40
41 Here's the word from the MySQL site
42 (http://www.mysql.com/doc/en/CREATE_TABLE.html):
43
44   CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
45   [table_options] [select_statement]
46   
47   or
48   
49   CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;
50   
51   create_definition:
52     col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
53               [PRIMARY KEY] [reference_definition]
54     or    PRIMARY KEY (index_col_name,...)
55     or    KEY [index_name] (index_col_name,...)
56     or    INDEX [index_name] (index_col_name,...)
57     or    UNIQUE [INDEX] [index_name] (index_col_name,...)
58     or    FULLTEXT [INDEX] [index_name] (index_col_name,...)
59     or    [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
60               [reference_definition]
61     or    CHECK (expr)
62   
63   type:
64           TINYINT[(length)] [UNSIGNED] [ZEROFILL]
65     or    SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
66     or    MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
67     or    INT[(length)] [UNSIGNED] [ZEROFILL]
68     or    INTEGER[(length)] [UNSIGNED] [ZEROFILL]
69     or    BIGINT[(length)] [UNSIGNED] [ZEROFILL]
70     or    REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
71     or    DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
72     or    FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
73     or    DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
74     or    NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
75     or    CHAR(length) [BINARY]
76     or    VARCHAR(length) [BINARY]
77     or    DATE
78     or    TIME
79     or    TIMESTAMP
80     or    DATETIME
81     or    TINYBLOB
82     or    BLOB
83     or    MEDIUMBLOB
84     or    LONGBLOB
85     or    TINYTEXT
86     or    TEXT
87     or    MEDIUMTEXT
88     or    LONGTEXT
89     or    ENUM(value1,value2,value3,...)
90     or    SET(value1,value2,value3,...)
91   
92   index_col_name:
93           col_name [(length)]
94   
95   reference_definition:
96           REFERENCES tbl_name [(index_col_name,...)]
97                      [MATCH FULL | MATCH PARTIAL]
98                      [ON DELETE reference_option]
99                      [ON UPDATE reference_option]
100   
101   reference_option:
102           RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
103   
104   table_options:
105           TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
106   or      AUTO_INCREMENT = #
107   or      AVG_ROW_LENGTH = #
108   or      CHECKSUM = {0 | 1}
109   or      COMMENT = "string"
110   or      MAX_ROWS = #
111   or      MIN_ROWS = #
112   or      PACK_KEYS = {0 | 1 | DEFAULT}
113   or      PASSWORD = "string"
114   or      DELAY_KEY_WRITE = {0 | 1}
115   or      ROW_FORMAT= { default | dynamic | fixed | compressed }
116   or      RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=#  RAID_CHUNKSIZE=#
117   or      UNION = (table_name,[table_name...])
118   or      INSERT_METHOD= {NO | FIRST | LAST }
119   or      DATA DIRECTORY="absolute path to directory"
120   or      INDEX DIRECTORY="absolute path to directory"
121
122 =cut
123
124 use strict;
125 use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
126 $VERSION = sprintf "%d.%02d", q$Revision: 1.19 $ =~ /(\d+)\.(\d+)/;
127 $DEBUG   = 0 unless defined $DEBUG;
128
129 use Data::Dumper;
130 use Parse::RecDescent;
131 use Exporter;
132 use base qw(Exporter);
133
134 @EXPORT_OK = qw(parse);
135
136 # Enable warnings within the Parse::RecDescent module.
137 $::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
138 $::RD_WARN   = 1; # Enable warnings. This will warn on unused rules &c.
139 $::RD_HINT   = 1; # Give out hints to help fix problems.
140
141 $GRAMMAR = q!
142
143
144     our ( %tables, $table_order );
145 }
146
147 #
148 # The "eofile" rule makes the parser fail if any "statement" rule
149 # fails.  Otherwise, the first successful match by a "statement" 
150 # won't cause the failure needed to know that the parse, as a whole,
151 # failed. -ky
152 #
153 startrule : statement(s) eofile { \%tables }
154
155 eofile : /^\Z/
156
157 statement : comment
158     | drop
159     | create
160     | <error>
161
162 drop : /drop/i WORD(s) ';'
163
164 create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
165     { 
166         my $table_name                       = $item{'table_name'};
167         $tables{ $table_name }{'order'}      = ++$table_order;
168         $tables{ $table_name }{'table_name'} = $table_name;
169
170         my $i = 1;
171         for my $definition ( @{ $item[7] } ) {
172             if ( $definition->{'type'} eq 'field' ) {
173                 my $field_name = $definition->{'name'};
174                 $tables{ $table_name }{'fields'}{ $field_name } = 
175                     { %$definition, order => $i };
176                 $i++;
177         
178                 if ( $definition->{'is_primary_key'} ) {
179                     push @{ $tables{ $table_name }{'indices'} },
180                         {
181                             type   => 'primary_key',
182                             fields => [ $field_name ],
183                         }
184                     ;
185                 }
186             }
187             elsif ( $definition->{'type'} eq 'foreign_key' ) {
188                 for my $field ( @{ $definition->{'fields'} } ) {
189                     push @{ 
190                         $tables{$table_name}{'fields'}{$field}{'constraints'}
191                     },
192                     $definition; 
193                 }
194             }
195             else {
196                 push @{ $tables{ $table_name }{'indices'} },
197                     $definition;
198             }
199         }
200
201         for my $opt ( @{ $item{'table_option(s?)'} } ) {
202             if ( my ( $key, $val ) = each %$opt ) {
203                 $tables{ $table_name }{'table_options'}{ $key } = $val;
204             }
205         }
206
207         1;
208     }
209
210 opt_if_not_exists : /if not exists/i
211
212 create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
213     {
214         push @{ $tables{ $item{'table_name'} }{'indices'} },
215             {
216                 name   => $item[4],
217                 type   => $item[2] ? 'unique' : 'normal',
218                 fields => $item[8],
219             }
220         ;
221     }
222
223 create_definition : index
224     | foreign_key 
225     | field
226     | <error>
227
228 comment : /^\s*(?:#|-{2}).*\n/
229
230 blank : /\s*/
231
232 field : field_name data_type field_qualifier(s?) reference_definition(?)
233     { 
234         my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
235         my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
236         delete $qualifiers{'not_null'};
237         if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
238             $qualifiers{ $_ } = 1 for @type_quals;
239         }
240
241         $return = { 
242             type           => 'field',
243             name           => $item{'field_name'}, 
244             data_type      => $item{'data_type'}{'type'},
245             size           => $item{'data_type'}{'size'},
246             list           => $item{'data_type'}{'list'},
247             null           => $null,
248             constraints    => $item{'reference_definition(?)'},
249             %qualifiers,
250         } 
251     }
252     | <error>
253
254 field_qualifier : not_null
255     { 
256         $return = { 
257              null => $item{'not_null'},
258         } 
259     }
260
261 field_qualifier : default_val
262     { 
263         $return = { 
264              default => $item{'default_val'},
265         } 
266     }
267
268 field_qualifier : auto_inc
269     { 
270         $return = { 
271              is_auto_inc => $item{'auto_inc'},
272         } 
273     }
274
275 field_qualifier : primary_key
276     { 
277         $return = { 
278              is_primary_key => $item{'primary_key'},
279         } 
280     }
281
282 field_qualifier : unsigned
283     { 
284         $return = { 
285              is_unsigned => $item{'unsigned'},
286         } 
287     }
288
289 reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
290     {
291         $return = {
292             type             => 'foreign_key',
293             reference_table  => $item[2],
294             reference_fields => $item[3][0],
295             match_type       => $item[4][0],
296             on_delete_do     => $item[5][0],
297             on_update_do     => $item[6][0],
298         }
299     }
300
301
302 match_type : /match full/i { 'match_full' }
303     |
304     /match partial/i { 'match_partial' }
305
306 on_delete_do : /on delete/i reference_option
307     { $item[2] }
308
309 on_update_do : /on update/i reference_option
310     { $item[2] }
311
312 reference_option: /restrict/i | 
313     /cascade/i   | 
314     /set null/i  | 
315     /no action/i | 
316     /set default/i
317     { $item[1] }  
318
319 index : primary_key_index
320     | unique_index
321     | fulltext_index
322     | normal_index
323     | <error>
324
325 table_name   : WORD
326
327 field_name   : WORD
328
329 index_name   : WORD
330
331 data_type    : WORD parens_value_list(s?) type_qualifier(s?)
332     { 
333         my $type = $item[1];
334         my $size; # field size, applicable only to non-set fields
335         my $list; # set list, applicable only to sets (duh)
336
337         if ( uc($type) =~ /^(SET|ENUM)$/ ) {
338             $size = undef;
339             $list = $item[2][0];
340         }
341         else {
342             $size = $item[2][0];
343             $list = [];
344         }
345
346         $return        = { 
347             type       => $type,
348             size       => $size,
349             list       => $list,
350             qualifiers => $item[3],
351         } 
352     }
353
354 parens_field_list : '(' field_name(s /,/) ')'
355     { $item[2] }
356
357 parens_value_list : '(' VALUE(s /,/) ')'
358     { $item[2] }
359
360 type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
361     { lc $item[1] }
362
363 field_type   : WORD
364
365 field_size   : '(' num_range ')' { $item{'num_range'} }
366
367 num_range    : DIGITS ',' DIGITS
368     { $return = $item[1].','.$item[3] }
369     | DIGITS
370     { $return = $item[1] }
371
372 create_index : /create/i /index/i
373
374 not_null     : /not/i /null/i { $return = 0 }
375
376 unsigned     : /unsigned/i { $return = 0 }
377
378 default_val  : /default/i /(?:')?[\w\d:.-]*(?:')?/ 
379     { 
380         $item[2] =~ s/'//g; 
381         $return  =  $item[2];
382     }
383
384 auto_inc : /auto_increment/i { 1 }
385
386 primary_key : /primary/i /key/i { 1 }
387
388 foreign_key : opt_constraint(?) /foreign key/i WORD(?) parens_field_list reference_definition
389     {
390         $return              =  {
391             type             => 'foreign_key',
392             name             => $item[3][0],
393             fields           => $item[4],
394             %{ $item{'reference_definition'} },
395         }
396     }
397
398 opt_constraint : /constraint/i WORD
399
400 primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
401     { 
402         $return    = { 
403             name   => $item{'index_name(?)'}[0],
404             type   => 'primary_key',
405             fields => $item[4],
406         };
407     }
408
409 normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
410     { 
411         $return    = { 
412             name   => $item{'index_name(?)'}[0],
413             type   => 'normal',
414             fields => $item[4],
415         } 
416     }
417
418 unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
419     { 
420         $return    = { 
421             name   => $item{'index_name(?)'}[0],
422             type   => 'unique',
423             fields => $item[5],
424         } 
425     }
426
427 fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
428     { 
429         $return    = { 
430             name   => $item{'index_name(?)'}[0],
431             type   => 'fulltext',
432             fields => $item[5],
433         } 
434     }
435
436 name_with_opt_paren : NAME parens_value_list(s?)
437     { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
438
439 fulltext : /fulltext/i { 1 }
440
441 unique : /unique/i { 1 }
442
443 key : /key/i | /index/i
444
445 table_option : /[^\s;]*/ 
446     { 
447         $return = { split /=/, $item[1] }
448     }
449
450 CREATE : /create/i
451
452 TEMPORARY : /temporary/i
453
454 TABLE : /table/i
455
456 WORD : /\w+/
457
458 DIGITS : /\d+/
459
460 COMMA : ','
461
462 NAME    : "`" /\w+/ "`"
463     { $item[2] }
464     | /\w+/
465     { $item[1] }
466
467 VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
468     { $item[1] }
469     | /'.*?'/   # XXX doesn't handle embedded quotes
470     { $item[1] }
471     | /NULL/
472     { 'NULL' }
473 #    {
474 #        {
475 #            value     => $item[1],
476 #            attribute => $item[2]
477 #        }
478 #    }
479
480 !;
481
482 # -------------------------------------------------------------------
483 sub parse {
484     my ( $translator, $data ) = @_;
485     my $parser = Parse::RecDescent->new($GRAMMAR);
486
487     local $::RD_TRACE  = $translator->trace ? 1 : undef;
488     local $DEBUG       = $translator->debug;
489
490     unless (defined $parser) {
491         return $translator->error("Error instantiating Parse::RecDescent ".
492             "instance: Bad grammer");
493     }
494
495     my $result = $parser->startrule($data);
496     return $translator->error( "Parse failed." ) unless defined $result;
497     warn Dumper( $result ) if $DEBUG;
498
499     my $schema = $translator->schema;
500     for my $table_name ( keys %{ $result } ) {
501         my $tdata =  $result->{ $table_name };
502         my $table =  $schema->add_table( 
503             name  => $tdata->{'table_name'},
504         ) or die $schema->error;
505
506         my @fields = sort { 
507             $tdata->{'fields'}->{$a}->{'order'} 
508             <=>
509             $tdata->{'fields'}->{$b}->{'order'}
510         } keys %{ $tdata->{'fields'} };
511
512         for my $fname ( @fields ) {
513             my $fdata = $tdata->{'fields'}{ $fname };
514             my $field = $table->add_field(
515                 name              => $fdata->{'name'},
516                 data_type         => $fdata->{'data_type'},
517                 size              => $fdata->{'size'},
518                 default_value     => $fdata->{'default'},
519                 is_auto_increment => $fdata->{'is_auto_inc'},
520                 is_nullable       => $fdata->{'null'},
521             ) or die $table->error;
522         }
523     }
524
525     return $result;
526 }
527
528 1;
529
530 # ----------------------------------------------------
531 # Where man is not nature is barren.
532 # William Blake
533 # ----------------------------------------------------
534
535 =pod
536
537 =head1 AUTHOR
538
539 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
540 Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
541
542 =head1 SEE ALSO
543
544 perl(1), Parse::RecDescent, SQL::Translator::Schema.
545
546 =cut