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