sqlt-graph now has a --trace option.
[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:
b98bd48d 201 /(b'[01]{1,64}')/ |
202 /(b"[01]{1,64}")/
bc9932bf 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 :
0a2833d8 512 /on update/i CURRENT_TIMESTAMP
6fa97af6 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 :
0a2833d8 582 /default/i CURRENT_TIMESTAMP
6fa97af6 583 {
0a2833d8 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
0a2833d8 786# always a scalar-ref, so that it is treated as a function and not quoted by consumers
787CURRENT_TIMESTAMP :
788 /current_timestamp(\(\))?/i { \'CURRENT_TIMESTAMP' }
789 | /now\(\)/i { \'CURRENT_TIMESTAMP' }
ea93df61 790
9bf756df 791END_OF_GRAMMAR
16dc9970 792
d529894e 793sub parse {
70944bc5 794 my ( $translator, $data ) = @_;
bdf60588 795
796 # Enable warnings within the Parse::RecDescent module.
797 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
798 local $::RD_WARN = 1 unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
799 local $::RD_HINT = 1 unless defined $::RD_HINT; # Give out hints to help fix problems.
800
e099bee9 801 local $::RD_TRACE = $translator->trace ? 1 : undef;
802 local $DEBUG = $translator->debug;
d529894e 803
bdf60588 804 my $parser = ddl_parser_instance('MySQL');
ea93df61 805
f1fe509d 806 # Preprocess for MySQL-specific and not-before-version comments
807 # from mysqldump
808 my $parser_version = parse_mysql_version(
809 $translator->parser_args->{mysql_parser_version}, 'mysql'
810 ) || DEFAULT_PARSER_VERSION;
811
ea93df61 812 while ( $data =~
813 s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es
f1fe509d 814 ) {
815 # do nothing; is there a better way to write this? -- ky
816 }
d529894e 817
818 my $result = $parser->startrule($data);
40c1ade1 819 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 820 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 821
70944bc5 822 my $schema = $translator->schema;
13aec984 823 $schema->name($result->{'database_name'}) if $result->{'database_name'};
824
ea93df61 825 my @tables = sort {
826 $result->{'tables'}{ $a }{'order'}
827 <=>
13aec984 828 $result->{'tables'}{ $b }{'order'}
829 } keys %{ $result->{'tables'} };
034ecdec 830
831 for my $table_name ( @tables ) {
13aec984 832 my $tdata = $result->{tables}{ $table_name };
ea93df61 833 my $table = $schema->add_table(
8ccdeb42 834 name => $tdata->{'table_name'},
40c1ade1 835 ) or die $schema->error;
8ccdeb42 836
734dfc91 837 $table->comments( $tdata->{'comments'} );
f2cf1734 838
ea93df61 839 my @fields = sort {
840 $tdata->{'fields'}->{$a}->{'order'}
8ccdeb42 841 <=>
842 $tdata->{'fields'}->{$b}->{'order'}
843 } keys %{ $tdata->{'fields'} };
844
845 for my $fname ( @fields ) {
846 my $fdata = $tdata->{'fields'}{ $fname };
847 my $field = $table->add_field(
848 name => $fdata->{'name'},
849 data_type => $fdata->{'data_type'},
850 size => $fdata->{'size'},
851 default_value => $fdata->{'default'},
852 is_auto_increment => $fdata->{'is_auto_inc'},
853 is_nullable => $fdata->{'null'},
88b89793 854 comments => $fdata->{'comments'},
40c1ade1 855 ) or die $table->error;
f2cf1734 856
857 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
858
bd30a9a2 859 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 860 'character set', 'on update' ) {
f2cf1734 861 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
862 next if ref $val eq 'ARRAY' && !@$val;
863 $field->extra( $qual, $val );
864 }
865 }
866
bd356af8 867 if ( $fdata->{'has_index'} ) {
868 $table->add_index(
869 name => '',
870 type => 'NORMAL',
871 fields => $fdata->{'name'},
872 ) or die $table->error;
873 }
874
875 if ( $fdata->{'is_unique'} ) {
876 $table->add_constraint(
877 name => '',
878 type => 'UNIQUE',
879 fields => $fdata->{'name'},
880 ) or die $table->error;
881 }
882
f2cf1734 883 for my $cdata ( @{ $fdata->{'constraints'} } ) {
884 next unless $cdata->{'type'} eq 'foreign_key';
885 $cdata->{'fields'} ||= [ $field->name ];
886 push @{ $tdata->{'constraints'} }, $cdata;
887 }
07d6e5f7 888
f2cf1734 889 }
890
891 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
892 my $index = $table->add_index(
893 name => $idata->{'name'},
894 type => uc $idata->{'type'},
895 fields => $idata->{'fields'},
896 ) or die $table->error;
897 }
898
02a21f1a 899 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
f7f81963 900 my @cleaned_options;
f1fe509d 901 my @ignore_opts = $translator->parser_args->{'ignore_opts'}
902 ? split( /,/, $translator->parser_args->{'ignore_opts'} )
903 : ();
f7f81963 904 if (@ignore_opts) {
905 my $ignores = { map { $_ => 1 } @ignore_opts };
906 foreach my $option (@options) {
907 # make sure the option isn't in ignore list
908 my ($option_key) = keys %$option;
f1fe509d 909 if ( !exists $ignores->{$option_key} ) {
910 push @cleaned_options, $option;
911 }
f7f81963 912 }
913 } else {
914 @cleaned_options = @options;
915 }
916 $table->options( \@cleaned_options ) or die $table->error;
02a21f1a 917 }
918
f2cf1734 919 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
920 my $constraint = $table->add_constraint(
921 name => $cdata->{'name'},
922 type => $cdata->{'type'},
923 fields => $cdata->{'fields'},
924 reference_table => $cdata->{'reference_table'},
925 reference_fields => $cdata->{'reference_fields'},
926 match_type => $cdata->{'match_type'} || '',
ea93df61 927 on_delete => $cdata->{'on_delete'}
f1fe509d 928 || $cdata->{'on_delete_do'},
ea93df61 929 on_update => $cdata->{'on_update'}
f1fe509d 930 || $cdata->{'on_update_do'},
f2cf1734 931 ) or die $table->error;
8ccdeb42 932 }
07d6e5f7 933
ea93df61 934 # After the constrains and PK/idxs have been created,
f1fe509d 935 # we normalize fields
07d6e5f7 936 normalize_field($_) for $table->get_fields;
8ccdeb42 937 }
ea93df61 938
939 my @procedures = sort {
940 $result->{procedures}->{ $a }->{'order'}
941 <=>
f1fe509d 942 $result->{procedures}->{ $b }->{'order'}
d31c185b 943 } keys %{ $result->{procedures} };
f1fe509d 944
945 for my $proc_name ( @procedures ) {
07d6e5f7 946 $schema->add_procedure(
947 name => $proc_name,
948 owner => $result->{procedures}->{$proc_name}->{owner},
949 sql => $result->{procedures}->{$proc_name}->{sql},
950 );
d31c185b 951 }
ea93df61 952 my @views = sort {
953 $result->{views}->{ $a }->{'order'}
954 <=>
f1fe509d 955 $result->{views}->{ $b }->{'order'}
d31c185b 956 } keys %{ $result->{views} };
f1fe509d 957
3ebe2ce5 958 for my $view_name ( @views ) {
07d6e5f7 959 $schema->add_view(
960 name => $view_name,
f1fe509d 961 sql => $result->{'views'}->{$view_name}->{sql},
07d6e5f7 962 );
d31c185b 963 }
8ccdeb42 964
f62bd16c 965 return 1;
d529894e 966}
967
ea93df61 968# Takes a field, and returns
07d6e5f7 969sub normalize_field {
970 my ($field) = @_;
936e626b 971 my ($size, $type, $list, $unsigned, $changed);
ea93df61 972
07d6e5f7 973 $size = $field->size;
974 $type = $field->data_type;
975 $list = $field->extra->{list} || [];
936e626b 976 $unsigned = defined($field->extra->{unsigned});
07d6e5f7 977
978 if ( !ref $size && $size eq 0 ) {
979 if ( lc $type eq 'tinyint' ) {
936e626b 980 $changed = $size != 4 - $unsigned;
981 $size = 4 - $unsigned;
07d6e5f7 982 }
983 elsif ( lc $type eq 'smallint' ) {
936e626b 984 $changed = $size != 6 - $unsigned;
985 $size = 6 - $unsigned;
07d6e5f7 986 }
987 elsif ( lc $type eq 'mediumint' ) {
936e626b 988 $changed = $size != 9 - $unsigned;
989 $size = 9 - $unsigned;
07d6e5f7 990 }
991 elsif ( $type =~ /^int(eger)?$/i ) {
936e626b 992 $changed = $size != 11 - $unsigned || $type ne 'int';
07d6e5f7 993 $type = 'int';
936e626b 994 $size = 11 - $unsigned;
07d6e5f7 995 }
996 elsif ( lc $type eq 'bigint' ) {
997 $changed = $size != 20;
998 $size = 20;
999 }
1000 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
1001 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
ea93df61 1002 $changed = @$old_size != 2
1003 || $old_size->[0] != 8
f1fe509d 1004 || $old_size->[1] != 2;
1005 $size = [8,2];
07d6e5f7 1006 }
1007 }
1008
1009 if ( $type =~ /^tiny(text|blob)$/i ) {
1010 $changed = $size != 255;
1011 $size = 255;
1012 }
1013 elsif ( $type =~ /^(blob|text)$/i ) {
1014 $changed = $size != 65_535;
1015 $size = 65_535;
1016 }
1017 elsif ( $type =~ /^medium(blob|text)$/i ) {
1018 $changed = $size != 16_777_215;
1019 $size = 16_777_215;
1020 }
1021 elsif ( $type =~ /^long(blob|text)$/i ) {
1022 $changed = $size != 4_294_967_295;
1023 $size = 4_294_967_295;
1024 }
f1fe509d 1025
9ab59f87 1026 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
1027 my %extra = $field->extra;
1028 my $longest = 0;
1029 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
1030 $longest = $len if $len > $longest;
1031 }
1032 $changed = 1;
1033 $size = $longest if $longest;
1034 }
1035
1036
f1fe509d 1037 if ( $changed ) {
1038 # We only want to clone the field, not *everything*
1039 {
1040 local $field->{table} = undef;
1041 $field->parsed_field( dclone( $field ) );
1042 $field->parsed_field->{table} = $field->table;
1043 }
1044 $field->size( $size );
1045 $field->data_type( $type );
1046 $field->sql_data_type( $type_mapping{ lc $type } )
1047 if exists $type_mapping{ lc $type };
1048 $field->extra->{list} = $list if @$list;
07d6e5f7 1049 }
1050}
1051
d529894e 10521;
1053
034ecdec 1054# -------------------------------------------------------------------
d529894e 1055# Where man is not nature is barren.
1056# William Blake
034ecdec 1057# -------------------------------------------------------------------
16dc9970 1058
d529894e 1059=pod
16dc9970 1060
1061=head1 AUTHOR
1062
19c5bc53 1063Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 1064Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 1065
1066=head1 SEE ALSO
1067
19c5bc53 1068Parse::RecDescent, SQL::Translator::Schema.
16dc9970 1069
1070=cut