Added grammar for "REFERENCES" (foreign keys).
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
1 package SQL::Translator::Parser::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.13 2003-04-02 01:46:36 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.13 $ =~ /(\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 my $parser; # should we do this?  There's no programmic way to 
142             # change the grammar, so I think this is safe.
143
144 $GRAMMAR = q!
145
146 { our ( %tables, $table_order ) }
147
148 #
149 # The "eofile" rule makes the parser fail if any "statement" rule
150 # fails.  Otherwise, the first successful match by a "statement" 
151 # won't cause the failure needed to know that the parse, as a whole,
152 # failed. -ky
153 #
154 startrule : statement(s) eofile { \%tables }
155
156 eofile : /^\Z/
157
158 statement : comment
159     | drop
160     | create
161     | <error>
162
163 drop : /drop/i WORD(s) ';'
164
165 create : create_table table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
166     { 
167         my $table_name                       = $item{'table_name'};
168         $tables{ $table_name }{'order'}      = ++$table_order;
169         $tables{ $table_name }{'table_name'} = $table_name;
170
171         my $i = 1;
172         for my $definition ( @{ $item[4] } ) {
173             if ( $definition->{'type'} eq 'field' ) {
174                 my $field_name = $definition->{'name'};
175                 $tables{ $table_name }{'fields'}{ $field_name } = 
176                     { %$definition, order => $i };
177                 $i++;
178         
179                 if ( $definition->{'is_primary_key'} ) {
180                     push @{ $tables{ $table_name }{'indices'} },
181                         {
182                             type   => 'primary_key',
183                             fields => [ $field_name ],
184                         }
185                     ;
186                 }
187             }
188             else {
189                 push @{ $tables{ $table_name }{'indices'} },
190                     $definition;
191             }
192         }
193
194         for my $opt ( @{ $item{'table_option'} } ) {
195             if ( my ( $key, $val ) = each %$opt ) {
196                 $tables{ $table_name }{'table_options'}{ $key } = $val;
197             }
198         }
199     }
200
201 create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
202     {
203         push @{ $tables{ $item{'table_name'} }{'indices'} },
204             {
205                 name   => $item[4],
206                 type   => $item[2] ? 'unique' : 'normal',
207                 fields => $item[8],
208             }
209         ;
210     }
211
212 create_definition : index
213     | field
214     | <error>
215
216 comment : /^\s*(?:#|-{2}).*\n/
217
218 blank : /\s*/
219
220 field : field_name data_type field_qualifier(s?) reference_definition(?)
221     { 
222         my %qualifiers = map { %$_ } @{ $item{'field_qualifier'} || [] };
223         my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
224         delete $qualifiers{'not_null'};
225         if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
226             $qualifiers{ $_ } = 1 for @type_quals;
227         }
228
229         $return = { 
230             type           => 'field',
231             name           => $item{'field_name'}, 
232             data_type      => $item{'data_type'}{'type'},
233             size           => $item{'data_type'}{'size'},
234             list           => $item{'data_type'}{'list'},
235             null           => $null,
236             constraints    => $item{'reference_definition'},
237             %qualifiers,
238         } 
239     }
240     | <error>
241
242 field_qualifier : not_null
243     { 
244         $return = { 
245              null => $item{'not_null'},
246         } 
247     }
248
249 field_qualifier : default_val
250     { 
251         $return = { 
252              default => $item{'default_val'},
253         } 
254     }
255
256 field_qualifier : auto_inc
257     { 
258         $return = { 
259              is_auto_inc => $item{'auto_inc'},
260         } 
261     }
262
263 field_qualifier : primary_key
264     { 
265         $return = { 
266              is_primary_key => $item{'primary_key'},
267         } 
268     }
269
270 field_qualifier : unsigned
271     { 
272         $return = { 
273              is_unsigned => $item{'unsigned'},
274         } 
275     }
276
277 reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
278     {
279         $return              =  {
280             type             => 'foreign_key',
281             reference_table  => $item[2],
282             reference_fields => $item[3][0],
283             match_type       => $item[4][0],
284             on_delete_do     => $item[5][0],
285             on_update_do     => $item[6][0],
286         }
287     }
288
289 match_type : /match full/i { 'match_full' }
290     |
291     /match partial/i { 'match_partial' }
292
293 on_delete_do : /on delete/i reference_option
294     { $item[2] }
295
296 on_update_do : /on update/i reference_option
297     { $item[2] }
298
299 reference_option: /restrict/i | 
300     /cascade/i   | 
301     /set null/i  | 
302     /no action/i | 
303     /set default/i
304     { $item[1] }  
305
306 index : primary_key_index
307     | unique_index
308     | fulltext_index
309     | normal_index
310
311 table_name   : WORD
312
313 field_name   : WORD
314
315 index_name   : WORD
316
317 data_type    : WORD parens_value_list(s?) type_qualifier(s?)
318     { 
319         my $type = $item[1];
320         my $size; # field size, applicable only to non-set fields
321         my $list; # set list, applicable only to sets (duh)
322
323         if ( uc($type) =~ /^(SET|ENUM)$/ ) {
324             $size = undef;
325             $list = $item[2][0];
326         }
327         else {
328             $size = $item[2][0];
329             $list = [];
330         }
331
332         $return        = { 
333             type       => $type,
334             size       => $size,
335             list       => $list,
336             qualifiers => $item[3],
337         } 
338     }
339
340 parens_field_list : '(' field_name(s /,/) ')'
341     { $item[2] }
342
343 parens_value_list : '(' VALUE(s /,/) ')'
344     { $item[2] }
345
346 type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
347     { lc $item[1] }
348
349 field_type   : WORD
350
351 field_size   : '(' num_range ')' { $item{'num_range'} }
352
353 num_range    : DIGITS ',' DIGITS
354     { $return = $item[1].','.$item[3] }
355     | DIGITS
356     { $return = $item[1] }
357
358 create_table : /create/i /table/i
359
360 create_index : /create/i /index/i
361
362 not_null     : /not/i /null/i { $return = 0 }
363
364 unsigned     : /unsigned/i { $return = 0 }
365
366 default_val  : /default/i /(?:')?[\w\d.-]*(?:')?/ 
367     { 
368         $item[2] =~ s/'//g; 
369         $return  =  $item[2];
370     }
371
372 auto_inc : /auto_increment/i { 1 }
373
374 primary_key : /primary/i /key/i { 1 }
375
376 primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
377     { 
378         $return    = { 
379             name   => $item{'index_name'}[0],
380             type   => 'primary_key',
381             fields => $item[4],
382         } 
383     }
384
385 normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
386     { 
387         $return    = { 
388             name   => $item{'index_name'}[0],
389             type   => 'normal',
390             fields => $item[4],
391         } 
392     }
393
394 unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
395     { 
396         $return    = { 
397             name   => $item{'index_name'}[0],
398             type   => 'unique',
399             fields => $item[5],
400         } 
401     }
402
403 fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
404     { 
405         $return    = { 
406             name   => $item{'index_name'}[0],
407             type   => 'fulltext',
408             fields => $item[5],
409         } 
410     }
411
412 name_with_opt_paren : NAME parens_value_list(s?)
413     { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
414
415 fulltext : /fulltext/i { 1 }
416
417 unique : /unique/i { 1 }
418
419 key : /key/i | /index/i
420
421 table_option : /[^\s;]*/ 
422     { 
423         $return = { split /=/, $item[1] }
424     }
425
426 WORD : /\w+/
427
428 DIGITS : /\d+/
429
430 COMMA : ','
431
432 NAME    : "`" /\w+/ "`"
433     { $item[2] }
434     | /\w+/
435     { $item[1] }
436
437 VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
438     { $item[1] }
439     | /'.*?'/   # XXX doesn't handle embedded quotes
440     { $item[1] }
441     | /NULL/
442     { 'NULL' }
443 #    {
444 #        {
445 #            value     => $item[1],
446 #            attribute => $item[2]
447 #        }
448 #    }
449
450 !;
451
452 # -------------------------------------------------------------------
453 sub parse {
454     my ( $translator, $data ) = @_;
455     $parser ||= Parse::RecDescent->new($GRAMMAR);
456
457     $::RD_TRACE  = $translator->trace ? 1 : undef;
458     $DEBUG       = $translator->debug;
459
460     unless (defined $parser) {
461         return $translator->error("Error instantiating Parse::RecDescent ".
462             "instance: Bad grammer");
463     }
464
465     my $result = $parser->startrule($data);
466     die "Parse failed.\n" unless defined $result;
467     warn Dumper($result) if $DEBUG;
468     return $result;
469 }
470
471 1;
472
473 #-----------------------------------------------------
474 # Where man is not nature is barren.
475 # William Blake
476 #-----------------------------------------------------
477
478 =pod
479
480 =head1 AUTHOR
481
482 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
483 Chris Mungall
484
485 =head1 SEE ALSO
486
487 perl(1), Parse::RecDescent.
488
489 =cut