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