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