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