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