New MySQL 4 syntax allows field names to be in backticks (and this is the
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
0d41bc9b 4# $Id: MySQL.pm,v 1.28 2003-08-04 15:19:08 kycl4rk Exp $
49e1eb70 5# -------------------------------------------------------------------
abfa405a 6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
077ebf34 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# -------------------------------------------------------------------
16dc9970 24
d529894e 25=head1 NAME
26
27SQL::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
39The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
40
629b76f9 41Here'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
d529894e 122=cut
123
16dc9970 124use strict;
d529894e 125use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
0d41bc9b 126$VERSION = sprintf "%d.%02d", q$Revision: 1.28 $ =~ /(\d+)\.(\d+)/;
8d0f3086 127$DEBUG = 0 unless defined $DEBUG;
077ebf34 128
d529894e 129use Data::Dumper;
077ebf34 130use Parse::RecDescent;
131use Exporter;
132use base qw(Exporter);
133
134@EXPORT_OK = qw(parse);
135
d529894e 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
d529894e 141$GRAMMAR = q!
142
8ccdeb42 143{
144 our ( %tables, $table_order );
145}
d529894e 146
629b76f9 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#
153startrule : statement(s) eofile { \%tables }
154
155eofile : /^\Z/
d529894e 156
157statement : comment
dcb4fa06 158 | use
61745327 159 | drop
d529894e 160 | create
161 | <error>
162
dcb4fa06 163use : /use/i WORD ';'
164
61745327 165drop : /drop/i WORD(s) ';'
166
dcb4fa06 167create : CREATE /database/i WORD ';'
168
40c1ade1 169create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
d529894e 170 {
171 my $table_name = $item{'table_name'};
172 $tables{ $table_name }{'order'} = ++$table_order;
173 $tables{ $table_name }{'table_name'} = $table_name;
174
61745327 175 my $i = 1;
40c1ade1 176 for my $definition ( @{ $item[7] } ) {
f2cf1734 177 if ( $definition->{'supertype'} eq 'field' ) {
178
d529894e 179 my $field_name = $definition->{'name'};
180 $tables{ $table_name }{'fields'}{ $field_name } =
181 { %$definition, order => $i };
182 $i++;
183
184 if ( $definition->{'is_primary_key'} ) {
f2cf1734 185 push @{ $tables{ $table_name }{'constraints'} },
d529894e 186 {
187 type => 'primary_key',
188 fields => [ $field_name ],
16dc9970 189 }
d529894e 190 ;
191 }
dd2ef5ae 192 }
f2cf1734 193 elsif ( $definition->{'supertype'} eq 'constraint' ) {
194 # prob get rid of this?
195# for my $field ( @{ $definition->{'fields'} } ) {
196# push @{
197# $tables{$table_name}{'fields'}{$field}{'constraints'}
198# },
199# $definition;
200# }
201
202 # this should be the only one needed
203 push @{ $tables{ $table_name }{'constraints'} }, $definition;
40c1ade1 204 }
f2cf1734 205 elsif ( $definition->{'supertype'} eq 'index' ) {
d529894e 206 push @{ $tables{ $table_name }{'indices'} },
207 $definition;
dd2ef5ae 208 }
d529894e 209 }
dd2ef5ae 210
58a88238 211 for my $opt ( @{ $item{'table_option(s?)'} } ) {
d529894e 212 if ( my ( $key, $val ) = each %$opt ) {
213 $tables{ $table_name }{'table_options'}{ $key } = $val;
dd2ef5ae 214 }
d529894e 215 }
58a88238 216
217 1;
d529894e 218 }
dd2ef5ae 219
40c1ade1 220opt_if_not_exists : /if not exists/i
221
f2cf1734 222create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
d529894e 223 {
224 push @{ $tables{ $item{'table_name'} }{'indices'} },
225 {
226 name => $item[4],
227 type => $item[2] ? 'unique' : 'normal',
228 fields => $item[8],
dd2ef5ae 229 }
d529894e 230 ;
231 }
dd2ef5ae 232
f2cf1734 233create_definition : constraint
234 | index
d529894e 235 | field
236 | <error>
237
88b89793 238comment : /^\s*(?:#|-{2}).*\n/ {
239 my $comment = $item[1];
240 $comment =~ s/^\s*(#|-{2})//;
241 $comment =~ s/\s*$//;
242 $return = $comment;
243}
d529894e 244
245blank : /\s*/
246
88b89793 247field : comment(s?) field_name data_type field_qualifier(s?) reference_definition(?) comment(s?)
d529894e 248 {
58a88238 249 my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
d529894e 250 my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
251 delete $qualifiers{'not_null'};
252 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
253 $qualifiers{ $_ } = 1 for @type_quals;
254 }
255
88b89793 256 my @comments = ( @{ $item[1] }, @{ $item[6] } );
257
d529894e 258 $return = {
f2cf1734 259 supertype => 'field',
260 name => $item{'field_name'},
261 data_type => $item{'data_type'}{'type'},
262 size => $item{'data_type'}{'size'},
263 list => $item{'data_type'}{'list'},
264 null => $null,
265 constraints => $item{'reference_definition(?)'},
88b89793 266 comments => [ @comments ],
d529894e 267 %qualifiers,
268 }
269 }
270 | <error>
dd2ef5ae 271
d529894e 272field_qualifier : not_null
273 {
274 $return = {
275 null => $item{'not_null'},
276 }
277 }
16dc9970 278
d529894e 279field_qualifier : default_val
280 {
281 $return = {
282 default => $item{'default_val'},
283 }
284 }
16dc9970 285
d529894e 286field_qualifier : auto_inc
287 {
288 $return = {
289 is_auto_inc => $item{'auto_inc'},
290 }
291 }
16dc9970 292
d529894e 293field_qualifier : primary_key
294 {
295 $return = {
296 is_primary_key => $item{'primary_key'},
297 }
298 }
16dc9970 299
d529894e 300field_qualifier : unsigned
301 {
302 $return = {
303 is_unsigned => $item{'unsigned'},
304 }
305 }
16dc9970 306
095b4549 307field_qualifier : /character set/i WORD
308 {
309 $return = {
310 character_set => $item[2],
311 }
312 }
313
658637cd 314reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
315 {
40c1ade1 316 $return = {
658637cd 317 type => 'foreign_key',
318 reference_table => $item[2],
319 reference_fields => $item[3][0],
320 match_type => $item[4][0],
321 on_delete_do => $item[5][0],
322 on_update_do => $item[6][0],
323 }
324 }
325
326match_type : /match full/i { 'match_full' }
327 |
328 /match partial/i { 'match_partial' }
329
330on_delete_do : /on delete/i reference_option
331 { $item[2] }
332
333on_update_do : /on update/i reference_option
334 { $item[2] }
335
336reference_option: /restrict/i |
337 /cascade/i |
338 /set null/i |
339 /no action/i |
340 /set default/i
341 { $item[1] }
342
f2cf1734 343index : normal_index
371f5f88 344 | fulltext_index
58a88238 345 | <error>
d529894e 346
0d41bc9b 347table_name : NAME
d529894e 348
0d41bc9b 349field_name : NAME
d529894e 350
351index_name : WORD
352
353data_type : WORD parens_value_list(s?) type_qualifier(s?)
354 {
355 my $type = $item[1];
356 my $size; # field size, applicable only to non-set fields
357 my $list; # set list, applicable only to sets (duh)
358
44fcd0b5 359 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 360 $size = undef;
361 $list = $item[2][0];
362 }
363 else {
364 $size = $item[2][0];
365 $list = [];
366 }
367
6333c482 368 unless ( @{ $size || [] } ) {
369 if ( lc $type eq 'tinyint' ) {
370 $size = [4];
371 }
372 elsif ( lc $type eq 'smallint' ) {
373 $size = [6];
374 }
375 elsif ( lc $type eq 'mediumint' ) {
376 $size = [9];
377 }
c736c39c 378 elsif ( $type =~ /^int(eger)?$/ ) {
f2cf1734 379 $type = 'int';
6333c482 380 $size = [11];
381 }
382 elsif ( lc $type eq 'bigint' ) {
383 $size = [20];
384 }
c736c39c 385 elsif ( lc $type =~ /(float|double|decimal|numeric|real)/ ) {
6333c482 386 $size = [8,2];
387 }
388 }
389
d529894e 390 $return = {
391 type => $type,
392 size => $size,
393 list => $list,
394 qualifiers => $item[3],
395 }
396 }
16dc9970 397
658637cd 398parens_field_list : '(' field_name(s /,/) ')'
399 { $item[2] }
400
d529894e 401parens_value_list : '(' VALUE(s /,/) ')'
402 { $item[2] }
16dc9970 403
d529894e 404type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
405 { lc $item[1] }
16dc9970 406
d529894e 407field_type : WORD
16dc9970 408
d529894e 409create_index : /create/i /index/i
dd2ef5ae 410
d529894e 411not_null : /not/i /null/i { $return = 0 }
16dc9970 412
d529894e 413unsigned : /unsigned/i { $return = 0 }
16dc9970 414
be019aae 415default_val : /default/i /(?:')?[\w\d:.-]*(?:')?/
d529894e 416 {
417 $item[2] =~ s/'//g;
418 $return = $item[2];
419 }
16dc9970 420
d529894e 421auto_inc : /auto_increment/i { 1 }
16dc9970 422
d529894e 423primary_key : /primary/i /key/i { 1 }
16dc9970 424
f2cf1734 425constraint : primary_key_def
426 | unique_key_def
427 | foreign_key_def
428 | <error>
429
430foreign_key_def : opt_constraint(?) /foreign key/i WORD(?) parens_field_list reference_definition
40c1ade1 431 {
432 $return = {
f2cf1734 433 supertype => 'constraint',
40c1ade1 434 type => 'foreign_key',
435 name => $item[3][0],
436 fields => $item[4],
437 %{ $item{'reference_definition'} },
438 }
439 }
440
441opt_constraint : /constraint/i WORD
442
f2cf1734 443primary_key_def : primary_key index_name(?) '(' field_name(s /,/) ')'
d529894e 444 {
f2cf1734 445 $return = {
446 supertype => 'constraint',
447 name => $item{'index_name(?)'}[0],
448 type => 'primary_key',
449 fields => $item[4],
58a88238 450 };
d529894e 451 }
16dc9970 452
f2cf1734 453unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 454 {
f2cf1734 455 $return = {
456 supertype => 'constraint',
457 name => $item{'index_name(?)'}[0],
458 type => 'unique',
459 fields => $item[5],
d529894e 460 }
461 }
16dc9970 462
f2cf1734 463normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 464 {
f2cf1734 465 $return = {
466 supertype => 'index',
467 type => 'normal',
468 name => $item{'index_name(?)'}[0],
469 fields => $item[4],
d529894e 470 }
471 }
16dc9970 472
f2cf1734 473fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
371f5f88 474 {
f2cf1734 475 $return = {
476 supertype => 'index',
477 type => 'fulltext',
478 name => $item{'index_name(?)'}[0],
479 fields => $item[5],
371f5f88 480 }
481 }
482
d529894e 483name_with_opt_paren : NAME parens_value_list(s?)
484 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 485
f2cf1734 486UNIQUE : /unique/i { 1 }
16dc9970 487
f2cf1734 488KEY : /key/i | /index/i
16dc9970 489
44fcd0b5 490table_option : /[^\s;]*/
d529894e 491 {
492 $return = { split /=/, $item[1] }
493 }
16dc9970 494
40c1ade1 495CREATE : /create/i
496
497TEMPORARY : /temporary/i
498
499TABLE : /table/i
500
d529894e 501WORD : /\w+/
16dc9970 502
d529894e 503DIGITS : /\d+/
16dc9970 504
d529894e 505COMMA : ','
16dc9970 506
d529894e 507NAME : "`" /\w+/ "`"
508 { $item[2] }
509 | /\w+/
510 { $item[1] }
16dc9970 511
d529894e 512VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
513 { $item[1] }
f2cf1734 514 | /'.*?'/
515 {
516 # remove leading/trailing quotes
517 my $val = $item[1];
518 $val =~ s/^['"]|['"]$//g;
519 $return = $val;
520 }
d529894e 521 | /NULL/
522 { 'NULL' }
16dc9970 523
d529894e 524!;
16dc9970 525
d529894e 526# -------------------------------------------------------------------
527sub parse {
70944bc5 528 my ( $translator, $data ) = @_;
40c1ade1 529 my $parser = Parse::RecDescent->new($GRAMMAR);
077ebf34 530
e099bee9 531 local $::RD_TRACE = $translator->trace ? 1 : undef;
532 local $DEBUG = $translator->debug;
d529894e 533
534 unless (defined $parser) {
535 return $translator->error("Error instantiating Parse::RecDescent ".
536 "instance: Bad grammer");
537 }
538
539 my $result = $parser->startrule($data);
40c1ade1 540 return $translator->error( "Parse failed." ) unless defined $result;
8ccdeb42 541 warn Dumper( $result ) if $DEBUG;
542
70944bc5 543 my $schema = $translator->schema;
034ecdec 544 my @tables = sort {
545 $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
546 } keys %{ $result };
547
548 for my $table_name ( @tables ) {
8ccdeb42 549 my $tdata = $result->{ $table_name };
550 my $table = $schema->add_table(
551 name => $tdata->{'table_name'},
40c1ade1 552 ) or die $schema->error;
8ccdeb42 553
f2cf1734 554# for my $opt ( @{ $tdata->{'table_options'} } ) {
555# if ( my ( $key, $val ) = each %$opt ) {
556# $tables->options(
557# }
558# }
559
8ccdeb42 560 my @fields = sort {
561 $tdata->{'fields'}->{$a}->{'order'}
562 <=>
563 $tdata->{'fields'}->{$b}->{'order'}
564 } keys %{ $tdata->{'fields'} };
565
566 for my $fname ( @fields ) {
567 my $fdata = $tdata->{'fields'}{ $fname };
568 my $field = $table->add_field(
569 name => $fdata->{'name'},
570 data_type => $fdata->{'data_type'},
571 size => $fdata->{'size'},
572 default_value => $fdata->{'default'},
573 is_auto_increment => $fdata->{'is_auto_inc'},
574 is_nullable => $fdata->{'null'},
88b89793 575 comments => $fdata->{'comments'},
40c1ade1 576 ) or die $table->error;
f2cf1734 577
578 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
579
580 for my $qual ( qw[ binary unsigned zerofill list ] ) {
581 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
582 next if ref $val eq 'ARRAY' && !@$val;
583 $field->extra( $qual, $val );
584 }
585 }
586
587 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
588 my %extra = $field->extra;
589 my $longest;
590 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
591 $longest = $len if $len > $longest;
592 }
593 $field->size( $longest ) if $longest;
594 }
595
596 for my $cdata ( @{ $fdata->{'constraints'} } ) {
597 next unless $cdata->{'type'} eq 'foreign_key';
598 $cdata->{'fields'} ||= [ $field->name ];
599 push @{ $tdata->{'constraints'} }, $cdata;
600 }
601 }
602
603 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
604 my $index = $table->add_index(
605 name => $idata->{'name'},
606 type => uc $idata->{'type'},
607 fields => $idata->{'fields'},
608 ) or die $table->error;
609 }
610
611 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
612 my $constraint = $table->add_constraint(
613 name => $cdata->{'name'},
614 type => $cdata->{'type'},
615 fields => $cdata->{'fields'},
616 reference_table => $cdata->{'reference_table'},
617 reference_fields => $cdata->{'reference_fields'},
618 match_type => $cdata->{'match_type'} || '',
619 on_delete => $cdata->{'on_delete_do'},
620 on_update => $cdata->{'on_update_do'},
621 ) or die $table->error;
8ccdeb42 622 }
623 }
624
f62bd16c 625 return 1;
d529894e 626}
627
6281;
629
034ecdec 630# -------------------------------------------------------------------
d529894e 631# Where man is not nature is barren.
632# William Blake
034ecdec 633# -------------------------------------------------------------------
16dc9970 634
d529894e 635=pod
16dc9970 636
637=head1 AUTHOR
638
d529894e 639Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 640Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 641
642=head1 SEE ALSO
643
8ccdeb42 644perl(1), Parse::RecDescent, SQL::Translator::Schema.
16dc9970 645
646=cut