Name of unique keys are not written if empty
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
d529894e 3=head1 NAME
4
5SQL::Translator::Parser::MySQL - parser for MySQL
6
7=head1 SYNOPSIS
8
9 use SQL::Translator;
10 use SQL::Translator::Parser::MySQL;
11
12 my $translator = SQL::Translator->new;
13 $translator->parser("SQL::Translator::Parser::MySQL");
14
15=head1 DESCRIPTION
16
17The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
18
629b76f9 19Here's the word from the MySQL site
20(http://www.mysql.com/doc/en/CREATE_TABLE.html):
21
22 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
23 [table_options] [select_statement]
ea93df61 24
629b76f9 25 or
ea93df61 26
629b76f9 27 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;
ea93df61 28
629b76f9 29 create_definition:
30 col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
31 [PRIMARY KEY] [reference_definition]
32 or PRIMARY KEY (index_col_name,...)
33 or KEY [index_name] (index_col_name,...)
34 or INDEX [index_name] (index_col_name,...)
35 or UNIQUE [INDEX] [index_name] (index_col_name,...)
36 or FULLTEXT [INDEX] [index_name] (index_col_name,...)
37 or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
38 [reference_definition]
39 or CHECK (expr)
ea93df61 40
629b76f9 41 type:
42 TINYINT[(length)] [UNSIGNED] [ZEROFILL]
43 or SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
44 or MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
45 or INT[(length)] [UNSIGNED] [ZEROFILL]
46 or INTEGER[(length)] [UNSIGNED] [ZEROFILL]
47 or BIGINT[(length)] [UNSIGNED] [ZEROFILL]
48 or REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
49 or DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
50 or FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
51 or DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
52 or NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
53 or CHAR(length) [BINARY]
54 or VARCHAR(length) [BINARY]
55 or DATE
56 or TIME
57 or TIMESTAMP
58 or DATETIME
59 or TINYBLOB
60 or BLOB
61 or MEDIUMBLOB
62 or LONGBLOB
63 or TINYTEXT
64 or TEXT
65 or MEDIUMTEXT
66 or LONGTEXT
67 or ENUM(value1,value2,value3,...)
68 or SET(value1,value2,value3,...)
ea93df61 69
629b76f9 70 index_col_name:
71 col_name [(length)]
ea93df61 72
629b76f9 73 reference_definition:
74 REFERENCES tbl_name [(index_col_name,...)]
75 [MATCH FULL | MATCH PARTIAL]
76 [ON DELETE reference_option]
77 [ON UPDATE reference_option]
ea93df61 78
629b76f9 79 reference_option:
80 RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
ea93df61 81
629b76f9 82 table_options:
83 TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
a7f49dfb 84 or ENGINE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
629b76f9 85 or AUTO_INCREMENT = #
86 or AVG_ROW_LENGTH = #
a7f49dfb 87 or [ DEFAULT ] CHARACTER SET charset_name
629b76f9 88 or CHECKSUM = {0 | 1}
a7f49dfb 89 or COLLATE collation_name
629b76f9 90 or COMMENT = "string"
91 or MAX_ROWS = #
92 or MIN_ROWS = #
93 or PACK_KEYS = {0 | 1 | DEFAULT}
94 or PASSWORD = "string"
95 or DELAY_KEY_WRITE = {0 | 1}
96 or ROW_FORMAT= { default | dynamic | fixed | compressed }
97 or RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=# RAID_CHUNKSIZE=#
98 or UNION = (table_name,[table_name...])
99 or INSERT_METHOD= {NO | FIRST | LAST }
100 or DATA DIRECTORY="absolute path to directory"
101 or INDEX DIRECTORY="absolute path to directory"
102
a7f49dfb 103
13aec984 104A subset of the ALTER TABLE syntax that allows addition of foreign keys:
105
106 ALTER [IGNORE] TABLE tbl_name alter_specification [, alter_specification] ...
107
108 alter_specification:
109 ADD [CONSTRAINT [symbol]]
110 FOREIGN KEY [index_name] (index_col_name,...)
111 [reference_definition]
112
113A subset of INSERT that we ignore:
114
115 INSERT anything
116
5d666b31 117=head1 ARGUMENTS
118
119This parser takes a single optional parser_arg C<mysql_parser_version>, which
120provides the desired version for the target database. Any statement in the processed
121dump file, that is commented with a version higher than the one supplied, will be stripped.
122
123Valid version specifiers for C<mysql_parser_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
124
125More information about the MySQL comment-syntax: L<http://dev.mysql.com/doc/refman/5.0/en/comments.html>
126
127
d529894e 128=cut
129
16dc9970 130use strict;
f27f9229 131use warnings;
0c04c5a2 132our ( $DEBUG, $GRAMMAR, @EXPORT_OK );
133our $VERSION = '1.59';
8d0f3086 134$DEBUG = 0 unless defined $DEBUG;
077ebf34 135
d529894e 136use Data::Dumper;
077ebf34 137use Parse::RecDescent;
138use Exporter;
07d6e5f7 139use Storable qw(dclone);
6b2dbb1a 140use DBI qw(:sql_types);
077ebf34 141use base qw(Exporter);
142
5d666b31 143use SQL::Translator::Utils qw/parse_mysql_version/;
144
f1fe509d 145our %type_mapping = ();
6b2dbb1a 146
077ebf34 147@EXPORT_OK = qw(parse);
148
d529894e 149# Enable warnings within the Parse::RecDescent module.
150$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
151$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
152$::RD_HINT = 1; # Give out hints to help fix problems.
153
d31c185b 154use constant DEFAULT_PARSER_VERSION => 30000;
155
9bf756df 156$GRAMMAR = << 'END_OF_GRAMMAR';
d529894e 157
ea93df61 158{
f1fe509d 159 my ( $database_name, %tables, $table_order, @table_comments, %views,
160 $view_order, %procedures, $proc_order );
86318717 161 my $delimiter = ';';
8ccdeb42 162}
d529894e 163
629b76f9 164#
165# The "eofile" rule makes the parser fail if any "statement" rule
ea93df61 166# fails. Otherwise, the first successful match by a "statement"
629b76f9 167# won't cause the failure needed to know that the parse, as a whole,
168# failed. -ky
169#
ea93df61 170startrule : statement(s) eofile {
171 {
172 database_name => $database_name,
173 tables => \%tables,
174 views => \%views,
f1fe509d 175 procedures => \%procedures,
ea93df61 176 }
13aec984 177}
629b76f9 178
179eofile : /^\Z/
d529894e 180
181statement : comment
dcb4fa06 182 | use
33d0d6d4 183 | set
61745327 184 | drop
d529894e 185 | create
13aec984 186 | alter
187 | insert
86318717 188 | delimiter
189 | empty_statement
d529894e 190 | <error>
191
86318717 192use : /use/i WORD "$delimiter"
13aec984 193 {
194 $database_name = $item[2];
195 @table_comments = ();
196 }
dcb4fa06 197
86318717 198set : /set/i /[^;]+/ "$delimiter"
c5dabd71 199 { @table_comments = () }
734dfc91 200
86318717 201drop : /drop/i TABLE /[^;]+/ "$delimiter"
33d0d6d4 202
86318717 203drop : /drop/i WORD(s) "$delimiter"
c5dabd71 204 { @table_comments = () }
61745327 205
bc9932bf 206bit:
207 /(b'[01]+')/ |
208 /(b"[01]+")/
209
9bf756df 210string :
ea93df61 211 # MySQL strings, unlike common SQL strings, can be double-quoted or
212 # single-quoted, and you can escape the delmiters by doubling (but only the
9bf756df 213 # delimiter) or by backslashing.
214
215 /'(\\.|''|[^\\\'])*'/ |
216 /"(\\.|""|[^\\\"])*"/
217 # For reference, std sql str: /(?:(?:\')(?:[^\']*(?:(?:\'\')[^\']*)*)(?:\'))//
218
219nonstring : /[^;\'"]+/
220
9644fab3 221statement_body : string | nonstring
9bf756df 222
9644fab3 223insert : /insert/i statement_body(s?) "$delimiter"
13aec984 224
86318717 225delimiter : /delimiter/i /[\S]+/
07d6e5f7 226 { $delimiter = $item[2] }
86318717 227
228empty_statement : "$delimiter"
229
230alter : ALTER TABLE table_name alter_specification(s /,/) "$delimiter"
13aec984 231 {
232 my $table_name = $item{'table_name'};
233 die "Cannot ALTER table '$table_name'; it does not exist"
234 unless $tables{ $table_name };
ea93df61 235 for my $definition ( @{ $item[4] } ) {
13aec984 236 $definition->{'extra'}->{'alter'} = 1;
237 push @{ $tables{ $table_name }{'constraints'} }, $definition;
238 }
239 }
240
241alter_specification : ADD foreign_key_def
242 { $return = $item[2] }
243
86318717 244create : CREATE /database/i WORD "$delimiter"
c5dabd71 245 { @table_comments = () }
dcb4fa06 246
86318717 247create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) "$delimiter"
ea93df61 248 {
d529894e 249 my $table_name = $item{'table_name'};
22529e36 250 die "There is more than one definition for $table_name"
251 if ($tables{$table_name});
252
d529894e 253 $tables{ $table_name }{'order'} = ++$table_order;
254 $tables{ $table_name }{'table_name'} = $table_name;
255
734dfc91 256 if ( @table_comments ) {
257 $tables{ $table_name }{'comments'} = [ @table_comments ];
258 @table_comments = ();
259 }
260
61745327 261 my $i = 1;
40c1ade1 262 for my $definition ( @{ $item[7] } ) {
f2cf1734 263 if ( $definition->{'supertype'} eq 'field' ) {
d529894e 264 my $field_name = $definition->{'name'};
ea93df61 265 $tables{ $table_name }{'fields'}{ $field_name } =
d529894e 266 { %$definition, order => $i };
267 $i++;
ea93df61 268
d529894e 269 if ( $definition->{'is_primary_key'} ) {
f2cf1734 270 push @{ $tables{ $table_name }{'constraints'} },
d529894e 271 {
272 type => 'primary_key',
273 fields => [ $field_name ],
16dc9970 274 }
d529894e 275 ;
276 }
dd2ef5ae 277 }
f2cf1734 278 elsif ( $definition->{'supertype'} eq 'constraint' ) {
f2cf1734 279 push @{ $tables{ $table_name }{'constraints'} }, $definition;
40c1ade1 280 }
f2cf1734 281 elsif ( $definition->{'supertype'} eq 'index' ) {
734dfc91 282 push @{ $tables{ $table_name }{'indices'} }, $definition;
dd2ef5ae 283 }
d529894e 284 }
dd2ef5ae 285
02a21f1a 286 if ( my @options = @{ $item{'table_option(s?)'} } ) {
35843e6b 287 for my $option ( @options ) {
288 my ( $key, $value ) = each %$option;
289 if ( $key eq 'comment' ) {
290 push @{ $tables{ $table_name }{'comments'} }, $value;
291 }
292 else {
293 push @{ $tables{ $table_name }{'table_options'} }, $option;
294 }
295 }
d529894e 296 }
58a88238 297
298 1;
d529894e 299 }
dd2ef5ae 300
40c1ade1 301opt_if_not_exists : /if not exists/i
302
86318717 303create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' "$delimiter"
d529894e 304 {
734dfc91 305 @table_comments = ();
d529894e 306 push @{ $tables{ $item{'table_name'} }{'indices'} },
307 {
308 name => $item[4],
041e659f 309 type => $item[2][0] ? 'unique' : 'normal',
d529894e 310 fields => $item[8],
dd2ef5ae 311 }
d529894e 312 ;
313 }
dd2ef5ae 314
d31c185b 315create : CREATE /trigger/i NAME not_delimiter "$delimiter"
07d6e5f7 316 {
317 @table_comments = ();
318 }
d31c185b 319
320create : CREATE PROCEDURE NAME not_delimiter "$delimiter"
07d6e5f7 321 {
322 @table_comments = ();
d31c185b 323 my $func_name = $item[3];
324 my $owner = '';
325 my $sql = "$item[1] $item[2] $item[3] $item[4]";
ea93df61 326
d31c185b 327 $procedures{ $func_name }{'order'} = ++$proc_order;
328 $procedures{ $func_name }{'name'} = $func_name;
329 $procedures{ $func_name }{'owner'} = $owner;
330 $procedures{ $func_name }{'sql'} = $sql;
07d6e5f7 331 }
d31c185b 332
333PROCEDURE : /procedure/i
07d6e5f7 334 | /function/i
d31c185b 335
22b9814a 336create : CREATE replace(?) algorithm(?) /view/i NAME not_delimiter "$delimiter"
07d6e5f7 337 {
338 @table_comments = ();
22b9814a 339 my $view_name = $item[5];
9644fab3 340 my $sql = join(q{ }, grep { defined and length } $item[1], $item[2]->[0], $item[3]->[0])
22b9814a 341 . " $item[4] $item[5] $item[6]";
ea93df61 342
d31c185b 343 # Hack to strip database from function calls in SQL
344 $sql =~ s#`\w+`\.(`\w+`\()##g;
ea93df61 345
d31c185b 346 $views{ $view_name }{'order'} = ++$view_order;
347 $views{ $view_name }{'name'} = $view_name;
348 $views{ $view_name }{'sql'} = $sql;
07d6e5f7 349 }
d31c185b 350
22b9814a 351replace : /or replace/i
352
d31c185b 353algorithm : /algorithm/i /=/ WORD
07d6e5f7 354 {
355 $return = "$item[1]=$item[3]";
356 }
d31c185b 357
358not_delimiter : /.*?(?=$delimiter)/is
359
ea93df61 360create_definition : constraint
f2cf1734 361 | index
d529894e 362 | field
02a21f1a 363 | comment
d529894e 364 | <error>
365
ea93df61 366comment : /^\s*(?:#|-{2}).*\n/
367 {
734dfc91 368 my $comment = $item[1];
a82fa2cb 369 $comment =~ s/^\s*(#|--)\s*//;
734dfc91 370 $comment =~ s/\s*$//;
371 $return = $comment;
734dfc91 372 }
373
da9f2af8 374comment : /\/\*/ /.*?\*\//s
e78d62f2 375 {
376 my $comment = $item[2];
73212389 377 $comment = substr($comment, 0, -2);
e78d62f2 378 $comment =~ s/^\s*|\s*$//g;
379 $return = $comment;
380 }
ea93df61 381
382field_comment : /^\s*(?:#|-{2}).*\n/
383 {
734dfc91 384 my $comment = $item[1];
a82fa2cb 385 $comment =~ s/^\s*(#|--)\s*//;
734dfc91 386 $comment =~ s/\s*$//;
387 $return = $comment;
388 }
d529894e 389
35843e6b 390
391field_comment2 : /comment/i /'.*?'/
392 {
393 my $comment = $item[2];
394 $comment =~ s/^'//;
395 $comment =~ s/'$//;
396 $return = $comment;
397 }
398
d529894e 399blank : /\s*/
400
100684f3 401field : field_comment(s?) field_name data_type field_qualifier(s?) field_comment2(?) reference_definition(?) on_update(?) field_comment(s?)
ea93df61 402 {
734dfc91 403 my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
d529894e 404 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
405 $qualifiers{ $_ } = 1 for @type_quals;
406 }
407
ea93df61 408 my $null = defined $qualifiers{'not_null'}
c5dabd71 409 ? $qualifiers{'not_null'} : 1;
410 delete $qualifiers{'not_null'};
411
35843e6b 412 my @comments = ( @{ $item[1] }, @{ $item[5] }, @{ $item[8] } );
88b89793 413
ea93df61 414 $return = {
f2cf1734 415 supertype => 'field',
ea93df61 416 name => $item{'field_name'},
f2cf1734 417 data_type => $item{'data_type'}{'type'},
418 size => $item{'data_type'}{'size'},
419 list => $item{'data_type'}{'list'},
420 null => $null,
421 constraints => $item{'reference_definition(?)'},
88b89793 422 comments => [ @comments ],
d529894e 423 %qualifiers,
ea93df61 424 }
d529894e 425 }
426 | <error>
dd2ef5ae 427
d529894e 428field_qualifier : not_null
ea93df61 429 {
430 $return = {
d529894e 431 null => $item{'not_null'},
ea93df61 432 }
d529894e 433 }
16dc9970 434
d529894e 435field_qualifier : default_val
ea93df61 436 {
437 $return = {
d529894e 438 default => $item{'default_val'},
ea93df61 439 }
d529894e 440 }
16dc9970 441
d529894e 442field_qualifier : auto_inc
ea93df61 443 {
444 $return = {
d529894e 445 is_auto_inc => $item{'auto_inc'},
ea93df61 446 }
d529894e 447 }
16dc9970 448
d529894e 449field_qualifier : primary_key
ea93df61 450 {
451 $return = {
d529894e 452 is_primary_key => $item{'primary_key'},
ea93df61 453 }
d529894e 454 }
16dc9970 455
d529894e 456field_qualifier : unsigned
ea93df61 457 {
458 $return = {
d529894e 459 is_unsigned => $item{'unsigned'},
ea93df61 460 }
d529894e 461 }
16dc9970 462
ea93df61 463field_qualifier : /character set/i WORD
095b4549 464 {
465 $return = {
bd30a9a2 466 'CHARACTER SET' => $item[2],
467 }
468 }
469
470field_qualifier : /collate/i WORD
471 {
472 $return = {
473 COLLATE => $item[2],
474 }
475 }
476
477field_qualifier : /on update/i CURRENT_TIMESTAMP
478 {
479 $return = {
480 'ON UPDATE' => $item[2],
095b4549 481 }
482 }
483
bd356af8 484field_qualifier : /unique/i KEY(?)
485 {
486 $return = {
487 is_unique => 1,
488 }
489 }
490
491field_qualifier : KEY
492 {
493 $return = {
494 has_index => 1,
495 }
496 }
497
100684f3 498reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
658637cd 499 {
40c1ade1 500 $return = {
658637cd 501 type => 'foreign_key',
502 reference_table => $item[2],
503 reference_fields => $item[3][0],
504 match_type => $item[4][0],
100684f3 505 on_delete => $item[5][0],
506 on_update => $item[6][0],
658637cd 507 }
508 }
509
02a21f1a 510match_type : /match full/i { 'full' }
658637cd 511 |
02a21f1a 512 /match partial/i { 'partial' }
658637cd 513
100684f3 514on_delete : /on delete/i reference_option
658637cd 515 { $item[2] }
516
ea93df61 517on_update :
6fa97af6 518 /on update/i 'CURRENT_TIMESTAMP'
519 { $item[2] }
520 |
521 /on update/i reference_option
658637cd 522 { $item[2] }
523
ea93df61 524reference_option: /restrict/i |
525 /cascade/i |
526 /set null/i |
527 /no action/i |
658637cd 528 /set default/i
ea93df61 529 { $item[1] }
658637cd 530
f2cf1734 531index : normal_index
371f5f88 532 | fulltext_index
531652d6 533 | spatial_index
58a88238 534 | <error>
d529894e 535
0d41bc9b 536table_name : NAME
d529894e 537
0d41bc9b 538field_name : NAME
d529894e 539
02a21f1a 540index_name : NAME
d529894e 541
542data_type : WORD parens_value_list(s?) type_qualifier(s?)
ea93df61 543 {
d529894e 544 my $type = $item[1];
545 my $size; # field size, applicable only to non-set fields
546 my $list; # set list, applicable only to sets (duh)
547
44fcd0b5 548 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 549 $size = undef;
550 $list = $item[2][0];
551 }
552 else {
553 $size = $item[2][0];
554 $list = [];
555 }
556
256d534a 557
ea93df61 558 $return = {
d529894e 559 type => $type,
560 size => $size,
561 list => $list,
562 qualifiers => $item[3],
ea93df61 563 }
d529894e 564 }
16dc9970 565
658637cd 566parens_field_list : '(' field_name(s /,/) ')'
567 { $item[2] }
568
d529894e 569parens_value_list : '(' VALUE(s /,/) ')'
570 { $item[2] }
16dc9970 571
d529894e 572type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
573 { lc $item[1] }
16dc9970 574
d529894e 575field_type : WORD
16dc9970 576
d529894e 577create_index : /create/i /index/i
dd2ef5ae 578
ea93df61 579not_null : /not/i /null/i
e78d62f2 580 { $return = 0 }
581 |
582 /null/i
583 { $return = 1 }
16dc9970 584
d529894e 585unsigned : /unsigned/i { $return = 0 }
16dc9970 586
ea93df61 587default_val :
6fa97af6 588 /default/i 'CURRENT_TIMESTAMP'
589 {
0f4c1a5b 590 $return = \$item[2];
6fa97af6 591 }
592 |
bc9932bf 593 /default/i string
594 {
595 $item[2] =~ s/^\s*'|'\s*$//g or $item[2] =~ s/^\s*"|"\s*$//g;
596 $return = $item[2];
597 }
598 |
599 /default/i bit
600 {
601 $item[2] =~ s/b['"]([01]+)['"]/$1/g;
602 $return = $item[2];
603 }
604 |
605 /default/i /(?:')?[\w\d:.-]*(?:')?/
09fa21a6 606 {
d529894e 607 $return = $item[2];
608 }
16dc9970 609
d529894e 610auto_inc : /auto_increment/i { 1 }
16dc9970 611
d529894e 612primary_key : /primary/i /key/i { 1 }
16dc9970 613
f2cf1734 614constraint : primary_key_def
615 | unique_key_def
616 | foreign_key_def
617 | <error>
618
02a21f1a 619foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
40c1ade1 620 {
621 $return = {
f2cf1734 622 supertype => 'constraint',
40c1ade1 623 type => 'foreign_key',
02a21f1a 624 name => $item[1],
09fa21a6 625 fields => $item[2],
40c1ade1 626 %{ $item{'reference_definition'} },
627 }
628 }
629
e78d62f2 630foreign_key_def_begin : /constraint/i /foreign key/i WORD
631 { $return = $item[3] }
632 |
633 /constraint/i NAME /foreign key/i
634 { $return = $item[2] }
635 |
636 /constraint/i /foreign key/i
02a21f1a 637 { $return = '' }
638 |
e78d62f2 639 /foreign key/i WORD
02a21f1a 640 { $return = $item[2] }
641 |
642 /foreign key/i
643 { $return = '' }
40c1ade1 644
1687dad4 645primary_key_def : primary_key index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 646 {
647 $return = {
f2cf1734 648 supertype => 'constraint',
f2cf1734 649 type => 'primary_key',
1687dad4 650 fields => $item[4],
651 options => $item[2][0] || $item[6][0],
652 };
653 }
654 # In theory, and according to the doc, names should not be allowed here, but
655 # MySQL accept (and ignores) them, so we are not going to be less :)
656 | primary_key index_name_not_using(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
657 {
658 $return = {
659 supertype => 'constraint',
660 type => 'primary_key',
661 fields => $item[4],
662 options => $item[6][0],
58a88238 663 };
d529894e 664 }
16dc9970 665
62a64f6c 666unique_key_def : UNIQUE KEY(?) index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 667 {
668 $return = {
f2cf1734 669 supertype => 'constraint',
62a64f6c 670 name => $item[3][0],
f2cf1734 671 type => 'unique',
62a64f6c 672 fields => $item[6],
673 options => $item[4][0] || $item[8][0],
ea93df61 674 }
d529894e 675 }
16dc9970 676
62a64f6c 677normal_index : KEY index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 678 {
679 $return = {
f2cf1734 680 supertype => 'index',
681 type => 'normal',
fd11d3c5 682 name => $item[2][0],
683 fields => $item[5],
62a64f6c 684 options => $item[3][0] || $item[7][0],
685 }
d529894e 686 }
16dc9970 687
fd11d3c5 688index_name_not_using : QUOTED_NAME
62a64f6c 689 | /(\b(?!using)\w+\b)/ { $return = ($1 =~ /^using/i) ? undef : $1 }
fd11d3c5 690
691index_type : /using (btree|hash|rtree)/i { $return = uc $1 }
692
f2cf1734 693fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
ea93df61 694 {
695 $return = {
f2cf1734 696 supertype => 'index',
697 type => 'fulltext',
698 name => $item{'index_name(?)'}[0],
699 fields => $item[5],
ea93df61 700 }
371f5f88 701 }
702
531652d6 703spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
ea93df61 704 {
705 $return = {
531652d6 706 supertype => 'index',
707 type => 'spatial',
708 name => $item{'index_name(?)'}[0],
709 fields => $item[5],
ea93df61 710 }
531652d6 711 }
712
d529894e 713name_with_opt_paren : NAME parens_value_list(s?)
714 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 715
041e659f 716UNIQUE : /unique/i
16dc9970 717
f2cf1734 718KEY : /key/i | /index/i
16dc9970 719
19c5bc53 720table_option : /comment/i /=/ /'.*?'/
35843e6b 721 {
722 my $comment = $item[3];
723 $comment =~ s/^'//;
724 $comment =~ s/'$//;
725 $return = { comment => $comment };
726 }
bb4c66d1 727 | /(default )?(charset|character set)/i /\s*=?\s*/ WORD
ea93df61 728 {
bd30a9a2 729 $return = { 'CHARACTER SET' => $item[3] };
d529894e 730 }
a7f49dfb 731 | /collate/i WORD
732 {
733 $return = { 'COLLATE' => $item[2] }
734 }
9a96648f 735 | /union/i /\s*=\s*/ '(' table_name(s /,/) ')'
ea93df61 736 {
9a96648f 737 $return = { $item[1] => $item[4] };
738 }
47666977 739 | WORD /\s*=\s*/ MAYBE_QUOTED_WORD
740 {
19c5bc53 741 $return = { $item[1] => $item[3] };
742 }
47666977 743
744MAYBE_QUOTED_WORD: /\w+/
745 | /'(\w+)'/
746 { $return = $1 }
747 | /"(\w+)"/
748 { $return = $1 }
749
bd30a9a2 750default : /default/i
16dc9970 751
13aec984 752ADD : /add/i
753
754ALTER : /alter/i
755
40c1ade1 756CREATE : /create/i
757
758TEMPORARY : /temporary/i
759
760TABLE : /table/i
761
d529894e 762WORD : /\w+/
16dc9970 763
d529894e 764DIGITS : /\d+/
16dc9970 765
d529894e 766COMMA : ','
16dc9970 767
a7f49dfb 768BACKTICK : '`'
769
f1fe509d 770DOUBLE_QUOTE: '"'
771
fd11d3c5 772QUOTED_NAME : BACKTICK /[^`]+/ BACKTICK
d529894e 773 { $item[2] }
f1fe509d 774 | DOUBLE_QUOTE /[^"]+/ DOUBLE_QUOTE
775 { $item[2] }
16dc9970 776
fd11d3c5 777NAME: QUOTED_NAME
ea93df61 778 | /\w+/
fd11d3c5 779
780VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
d529894e 781 { $item[1] }
ea93df61 782 | /'.*?'/
783 {
784 # remove leading/trailing quotes
f2cf1734 785 my $val = $item[1];
786 $val =~ s/^['"]|['"]$//g;
787 $return = $val;
788 }
d529894e 789 | /NULL/
790 { 'NULL' }
16dc9970 791
bd30a9a2 792CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
07d6e5f7 793 | /now\(\)/i
794 { 'CURRENT_TIMESTAMP' }
ea93df61 795
9bf756df 796END_OF_GRAMMAR
16dc9970 797
d529894e 798sub parse {
70944bc5 799 my ( $translator, $data ) = @_;
40c1ade1 800 my $parser = Parse::RecDescent->new($GRAMMAR);
e099bee9 801 local $::RD_TRACE = $translator->trace ? 1 : undef;
802 local $DEBUG = $translator->debug;
d529894e 803
804 unless (defined $parser) {
805 return $translator->error("Error instantiating Parse::RecDescent ".
806 "instance: Bad grammer");
807 }
ea93df61 808
f1fe509d 809 # Preprocess for MySQL-specific and not-before-version comments
810 # from mysqldump
811 my $parser_version = parse_mysql_version(
812 $translator->parser_args->{mysql_parser_version}, 'mysql'
813 ) || DEFAULT_PARSER_VERSION;
814
ea93df61 815 while ( $data =~
816 s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es
f1fe509d 817 ) {
818 # do nothing; is there a better way to write this? -- ky
819 }
d529894e 820
821 my $result = $parser->startrule($data);
40c1ade1 822 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 823 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 824
70944bc5 825 my $schema = $translator->schema;
13aec984 826 $schema->name($result->{'database_name'}) if $result->{'database_name'};
827
ea93df61 828 my @tables = sort {
829 $result->{'tables'}{ $a }{'order'}
830 <=>
13aec984 831 $result->{'tables'}{ $b }{'order'}
832 } keys %{ $result->{'tables'} };
034ecdec 833
834 for my $table_name ( @tables ) {
13aec984 835 my $tdata = $result->{tables}{ $table_name };
ea93df61 836 my $table = $schema->add_table(
8ccdeb42 837 name => $tdata->{'table_name'},
40c1ade1 838 ) or die $schema->error;
8ccdeb42 839
734dfc91 840 $table->comments( $tdata->{'comments'} );
f2cf1734 841
ea93df61 842 my @fields = sort {
843 $tdata->{'fields'}->{$a}->{'order'}
8ccdeb42 844 <=>
845 $tdata->{'fields'}->{$b}->{'order'}
846 } keys %{ $tdata->{'fields'} };
847
848 for my $fname ( @fields ) {
849 my $fdata = $tdata->{'fields'}{ $fname };
850 my $field = $table->add_field(
851 name => $fdata->{'name'},
852 data_type => $fdata->{'data_type'},
853 size => $fdata->{'size'},
854 default_value => $fdata->{'default'},
855 is_auto_increment => $fdata->{'is_auto_inc'},
856 is_nullable => $fdata->{'null'},
88b89793 857 comments => $fdata->{'comments'},
40c1ade1 858 ) or die $table->error;
f2cf1734 859
860 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
861
bd30a9a2 862 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 863 'character set', 'on update' ) {
f2cf1734 864 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
865 next if ref $val eq 'ARRAY' && !@$val;
866 $field->extra( $qual, $val );
867 }
868 }
869
bd356af8 870 if ( $fdata->{'has_index'} ) {
871 $table->add_index(
872 name => '',
873 type => 'NORMAL',
874 fields => $fdata->{'name'},
875 ) or die $table->error;
876 }
877
878 if ( $fdata->{'is_unique'} ) {
879 $table->add_constraint(
880 name => '',
881 type => 'UNIQUE',
882 fields => $fdata->{'name'},
883 ) or die $table->error;
884 }
885
f2cf1734 886 for my $cdata ( @{ $fdata->{'constraints'} } ) {
887 next unless $cdata->{'type'} eq 'foreign_key';
888 $cdata->{'fields'} ||= [ $field->name ];
889 push @{ $tdata->{'constraints'} }, $cdata;
890 }
07d6e5f7 891
f2cf1734 892 }
893
894 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
895 my $index = $table->add_index(
896 name => $idata->{'name'},
897 type => uc $idata->{'type'},
898 fields => $idata->{'fields'},
899 ) or die $table->error;
900 }
901
02a21f1a 902 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
f7f81963 903 my @cleaned_options;
f1fe509d 904 my @ignore_opts = $translator->parser_args->{'ignore_opts'}
905 ? split( /,/, $translator->parser_args->{'ignore_opts'} )
906 : ();
f7f81963 907 if (@ignore_opts) {
908 my $ignores = { map { $_ => 1 } @ignore_opts };
909 foreach my $option (@options) {
910 # make sure the option isn't in ignore list
911 my ($option_key) = keys %$option;
f1fe509d 912 if ( !exists $ignores->{$option_key} ) {
913 push @cleaned_options, $option;
914 }
f7f81963 915 }
916 } else {
917 @cleaned_options = @options;
918 }
919 $table->options( \@cleaned_options ) or die $table->error;
02a21f1a 920 }
921
f2cf1734 922 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
923 my $constraint = $table->add_constraint(
924 name => $cdata->{'name'},
925 type => $cdata->{'type'},
926 fields => $cdata->{'fields'},
927 reference_table => $cdata->{'reference_table'},
928 reference_fields => $cdata->{'reference_fields'},
929 match_type => $cdata->{'match_type'} || '',
ea93df61 930 on_delete => $cdata->{'on_delete'}
f1fe509d 931 || $cdata->{'on_delete_do'},
ea93df61 932 on_update => $cdata->{'on_update'}
f1fe509d 933 || $cdata->{'on_update_do'},
f2cf1734 934 ) or die $table->error;
8ccdeb42 935 }
07d6e5f7 936
ea93df61 937 # After the constrains and PK/idxs have been created,
f1fe509d 938 # we normalize fields
07d6e5f7 939 normalize_field($_) for $table->get_fields;
8ccdeb42 940 }
ea93df61 941
942 my @procedures = sort {
943 $result->{procedures}->{ $a }->{'order'}
944 <=>
f1fe509d 945 $result->{procedures}->{ $b }->{'order'}
d31c185b 946 } keys %{ $result->{procedures} };
f1fe509d 947
948 for my $proc_name ( @procedures ) {
07d6e5f7 949 $schema->add_procedure(
950 name => $proc_name,
951 owner => $result->{procedures}->{$proc_name}->{owner},
952 sql => $result->{procedures}->{$proc_name}->{sql},
953 );
d31c185b 954 }
ea93df61 955 my @views = sort {
956 $result->{views}->{ $a }->{'order'}
957 <=>
f1fe509d 958 $result->{views}->{ $b }->{'order'}
d31c185b 959 } keys %{ $result->{views} };
f1fe509d 960
3ebe2ce5 961 for my $view_name ( @views ) {
07d6e5f7 962 $schema->add_view(
963 name => $view_name,
f1fe509d 964 sql => $result->{'views'}->{$view_name}->{sql},
07d6e5f7 965 );
d31c185b 966 }
8ccdeb42 967
f62bd16c 968 return 1;
d529894e 969}
970
ea93df61 971# Takes a field, and returns
07d6e5f7 972sub normalize_field {
973 my ($field) = @_;
974 my ($size, $type, $list, $changed) = @_;
ea93df61 975
07d6e5f7 976 $size = $field->size;
977 $type = $field->data_type;
978 $list = $field->extra->{list} || [];
979
980 if ( !ref $size && $size eq 0 ) {
981 if ( lc $type eq 'tinyint' ) {
982 $changed = $size != 4;
983 $size = 4;
984 }
985 elsif ( lc $type eq 'smallint' ) {
986 $changed = $size != 6;
987 $size = 6;
988 }
989 elsif ( lc $type eq 'mediumint' ) {
990 $changed = $size != 9;
991 $size = 9;
992 }
993 elsif ( $type =~ /^int(eger)?$/i ) {
994 $changed = $size != 11 || $type ne 'int';
995 $type = 'int';
996 $size = 11;
997 }
998 elsif ( lc $type eq 'bigint' ) {
999 $changed = $size != 20;
1000 $size = 20;
1001 }
1002 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
1003 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
ea93df61 1004 $changed = @$old_size != 2
1005 || $old_size->[0] != 8
f1fe509d 1006 || $old_size->[1] != 2;
1007 $size = [8,2];
07d6e5f7 1008 }
1009 }
1010
1011 if ( $type =~ /^tiny(text|blob)$/i ) {
1012 $changed = $size != 255;
1013 $size = 255;
1014 }
1015 elsif ( $type =~ /^(blob|text)$/i ) {
1016 $changed = $size != 65_535;
1017 $size = 65_535;
1018 }
1019 elsif ( $type =~ /^medium(blob|text)$/i ) {
1020 $changed = $size != 16_777_215;
1021 $size = 16_777_215;
1022 }
1023 elsif ( $type =~ /^long(blob|text)$/i ) {
1024 $changed = $size != 4_294_967_295;
1025 $size = 4_294_967_295;
1026 }
f1fe509d 1027
9ab59f87 1028 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
1029 my %extra = $field->extra;
1030 my $longest = 0;
1031 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
1032 $longest = $len if $len > $longest;
1033 }
1034 $changed = 1;
1035 $size = $longest if $longest;
1036 }
1037
1038
f1fe509d 1039 if ( $changed ) {
1040 # We only want to clone the field, not *everything*
1041 {
1042 local $field->{table} = undef;
1043 $field->parsed_field( dclone( $field ) );
1044 $field->parsed_field->{table} = $field->table;
1045 }
1046 $field->size( $size );
1047 $field->data_type( $type );
1048 $field->sql_data_type( $type_mapping{ lc $type } )
1049 if exists $type_mapping{ lc $type };
1050 $field->extra->{list} = $list if @$list;
07d6e5f7 1051 }
1052}
1053
d529894e 10541;
1055
034ecdec 1056# -------------------------------------------------------------------
d529894e 1057# Where man is not nature is barren.
1058# William Blake
034ecdec 1059# -------------------------------------------------------------------
16dc9970 1060
d529894e 1061=pod
16dc9970 1062
1063=head1 AUTHOR
1064
19c5bc53 1065Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 1066Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 1067
1068=head1 SEE ALSO
1069
19c5bc53 1070Parse::RecDescent, SQL::Translator::Schema.
16dc9970 1071
1072=cut