MariaDB allows defaults of column names
[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
da93ce68 135our $VERSION = '1.60';
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 }
70aafeed 695 |
696 /default/i NAME # column value, allowed in MariaDB
697 {
698 $return = $item[2];
699 }
16dc9970 700
d529894e 701auto_inc : /auto_increment/i { 1 }
16dc9970 702
d529894e 703primary_key : /primary/i /key/i { 1 }
16dc9970 704
f2cf1734 705constraint : primary_key_def
706 | unique_key_def
707 | foreign_key_def
51fe887d 708 | check_def
f2cf1734 709 | <error>
710
51fe887d 711expr : /[^)]* \( [^)]+ \) [^)]*/x # parens, balanced one deep
712 | /[^)]+/
713
714check_def : check_def_begin '(' expr ')'
715 {
716 $return = {
717 supertype => 'constraint',
718 type => 'check',
719 name => $item[1],
720 expression => $item[3],
721 }
722 }
723
724check_def_begin : /constraint/i /check/i NAME
725 { $return = $item[3] }
726 |
727 /constraint/i NAME /check/i
728 { $return = $item[2] }
729 |
730 /constraint/i /check/i
731 { $return = '' }
732
02a21f1a 733foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
40c1ade1 734 {
735 $return = {
f2cf1734 736 supertype => 'constraint',
40c1ade1 737 type => 'foreign_key',
02a21f1a 738 name => $item[1],
09fa21a6 739 fields => $item[2],
40c1ade1 740 %{ $item{'reference_definition'} },
741 }
742 }
743
aaea005b 744foreign_key_def_begin : /constraint/i /foreign key/i NAME
e78d62f2 745 { $return = $item[3] }
746 |
747 /constraint/i NAME /foreign key/i
748 { $return = $item[2] }
749 |
750 /constraint/i /foreign key/i
02a21f1a 751 { $return = '' }
752 |
aaea005b 753 /foreign key/i NAME
02a21f1a 754 { $return = $item[2] }
755 |
756 /foreign key/i
757 { $return = '' }
40c1ade1 758
1687dad4 759primary_key_def : primary_key index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 760 {
761 $return = {
f2cf1734 762 supertype => 'constraint',
f2cf1734 763 type => 'primary_key',
1687dad4 764 fields => $item[4],
765 options => $item[2][0] || $item[6][0],
766 };
767 }
768 # In theory, and according to the doc, names should not be allowed here, but
769 # MySQL accept (and ignores) them, so we are not going to be less :)
770 | primary_key index_name_not_using(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
771 {
772 $return = {
773 supertype => 'constraint',
774 type => 'primary_key',
775 fields => $item[4],
776 options => $item[6][0],
58a88238 777 };
d529894e 778 }
16dc9970 779
62a64f6c 780unique_key_def : UNIQUE KEY(?) index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 781 {
782 $return = {
f2cf1734 783 supertype => 'constraint',
62a64f6c 784 name => $item[3][0],
f2cf1734 785 type => 'unique',
62a64f6c 786 fields => $item[6],
787 options => $item[4][0] || $item[8][0],
ea93df61 788 }
d529894e 789 }
16dc9970 790
62a64f6c 791normal_index : KEY index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
ea93df61 792 {
793 $return = {
f2cf1734 794 supertype => 'index',
795 type => 'normal',
fd11d3c5 796 name => $item[2][0],
797 fields => $item[5],
62a64f6c 798 options => $item[3][0] || $item[7][0],
799 }
d529894e 800 }
16dc9970 801
fd11d3c5 802index_name_not_using : QUOTED_NAME
62a64f6c 803 | /(\b(?!using)\w+\b)/ { $return = ($1 =~ /^using/i) ? undef : $1 }
fd11d3c5 804
805index_type : /using (btree|hash|rtree)/i { $return = uc $1 }
806
f2cf1734 807fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
ea93df61 808 {
809 $return = {
f2cf1734 810 supertype => 'index',
811 type => 'fulltext',
812 name => $item{'index_name(?)'}[0],
813 fields => $item[5],
ea93df61 814 }
371f5f88 815 }
816
531652d6 817spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
ea93df61 818 {
819 $return = {
531652d6 820 supertype => 'index',
821 type => 'spatial',
822 name => $item{'index_name(?)'}[0],
823 fields => $item[5],
ea93df61 824 }
531652d6 825 }
826
d529894e 827name_with_opt_paren : NAME parens_value_list(s?)
828 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 829
041e659f 830UNIQUE : /unique/i
16dc9970 831
f2cf1734 832KEY : /key/i | /index/i
16dc9970 833
aaea005b 834table_option : /comment/i /=/ string
35843e6b 835 {
aaea005b 836 $return = { comment => $item[3] };
35843e6b 837 }
aaea005b 838 | /(default )?(charset|character set)/i /\s*=?\s*/ NAME
ea93df61 839 {
bd30a9a2 840 $return = { 'CHARACTER SET' => $item[3] };
d529894e 841 }
aaea005b 842 | /collate/i NAME
a7f49dfb 843 {
844 $return = { 'COLLATE' => $item[2] }
845 }
9a96648f 846 | /union/i /\s*=\s*/ '(' table_name(s /,/) ')'
ea93df61 847 {
9a96648f 848 $return = { $item[1] => $item[4] };
849 }
aaea005b 850 | WORD /\s*=\s*/ table_option_value
47666977 851 {
19c5bc53 852 $return = { $item[1] => $item[3] };
853 }
47666977 854
aaea005b 855table_option_value : VALUE
856 | NAME
47666977 857
bd30a9a2 858default : /default/i
16dc9970 859
13aec984 860ADD : /add/i
861
862ALTER : /alter/i
863
40c1ade1 864CREATE : /create/i
865
866TEMPORARY : /temporary/i
867
868TABLE : /table/i
869
d529894e 870WORD : /\w+/
16dc9970 871
d529894e 872DIGITS : /\d+/
16dc9970 873
d529894e 874COMMA : ','
16dc9970 875
a7f49dfb 876BACKTICK : '`'
877
f1fe509d 878DOUBLE_QUOTE: '"'
879
46417fe2 880SINGLE_QUOTE: "'"
881
aaea005b 882QUOTED_NAME : BQSTRING
883 | SQSTRING
884 | DQSTRING
885
886# MySQL strings, unlike common SQL strings, can have the delmiters
887# escaped either by doubling or by backslashing.
fd498bb0 888BQSTRING: BACKTICK <skip: ''> /(?:[^\\`]|``|\\.)*/ BACKTICK
889 { ($return = $item[3]) =~ s/(\\[\\`]|``)/substr($1,1)/ge }
aaea005b 890
fd498bb0 891DQSTRING: DOUBLE_QUOTE <skip: ''> /(?:[^\\"]|""|\\.)*/ DOUBLE_QUOTE
892 { ($return = $item[3]) =~ s/(\\[\\"]|"")/substr($1,1)/ge }
aaea005b 893
fd498bb0 894SQSTRING: SINGLE_QUOTE <skip: ''> /(?:[^\\']|''|\\.)*/ SINGLE_QUOTE
895 { ($return = $item[3]) =~ s/(\\[\\']|'')/substr($1,1)/ge }
aaea005b 896
16dc9970 897
fd11d3c5 898NAME: QUOTED_NAME
ea93df61 899 | /\w+/
fd11d3c5 900
aaea005b 901VALUE : /[-+]?\d*\.?\d+(?:[eE]\d+)?/
d529894e 902 { $item[1] }
aaea005b 903 | SQSTRING
904 | DQSTRING
905 | /NULL/i
d529894e 906 { 'NULL' }
16dc9970 907
0a2833d8 908# always a scalar-ref, so that it is treated as a function and not quoted by consumers
909CURRENT_TIMESTAMP :
910 /current_timestamp(\(\))?/i { \'CURRENT_TIMESTAMP' }
911 | /now\(\)/i { \'CURRENT_TIMESTAMP' }
ea93df61 912
9bf756df 913END_OF_GRAMMAR
16dc9970 914
d529894e 915sub parse {
70944bc5 916 my ( $translator, $data ) = @_;
bdf60588 917
918 # Enable warnings within the Parse::RecDescent module.
f9a2a1d9 919 # Make sure the parser dies when it encounters an error
920 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS;
921 # Enable warnings. This will warn on unused rules &c.
922 local $::RD_WARN = 1 unless defined $::RD_WARN;
923 # Give out hints to help fix problems.
924 local $::RD_HINT = 1 unless defined $::RD_HINT;
e099bee9 925 local $::RD_TRACE = $translator->trace ? 1 : undef;
926 local $DEBUG = $translator->debug;
d529894e 927
bdf60588 928 my $parser = ddl_parser_instance('MySQL');
ea93df61 929
f1fe509d 930 # Preprocess for MySQL-specific and not-before-version comments
931 # from mysqldump
932 my $parser_version = parse_mysql_version(
933 $translator->parser_args->{mysql_parser_version}, 'mysql'
934 ) || DEFAULT_PARSER_VERSION;
935
ea93df61 936 while ( $data =~
937 s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es
f1fe509d 938 ) {
939 # do nothing; is there a better way to write this? -- ky
940 }
d529894e 941
942 my $result = $parser->startrule($data);
40c1ade1 943 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 944 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 945
70944bc5 946 my $schema = $translator->schema;
13aec984 947 $schema->name($result->{'database_name'}) if $result->{'database_name'};
948
ea93df61 949 my @tables = sort {
950 $result->{'tables'}{ $a }{'order'}
951 <=>
13aec984 952 $result->{'tables'}{ $b }{'order'}
953 } keys %{ $result->{'tables'} };
034ecdec 954
955 for my $table_name ( @tables ) {
13aec984 956 my $tdata = $result->{tables}{ $table_name };
ea93df61 957 my $table = $schema->add_table(
8ccdeb42 958 name => $tdata->{'table_name'},
40c1ade1 959 ) or die $schema->error;
8ccdeb42 960
734dfc91 961 $table->comments( $tdata->{'comments'} );
f2cf1734 962
ea93df61 963 my @fields = sort {
964 $tdata->{'fields'}->{$a}->{'order'}
8ccdeb42 965 <=>
966 $tdata->{'fields'}->{$b}->{'order'}
967 } keys %{ $tdata->{'fields'} };
968
969 for my $fname ( @fields ) {
970 my $fdata = $tdata->{'fields'}{ $fname };
971 my $field = $table->add_field(
972 name => $fdata->{'name'},
973 data_type => $fdata->{'data_type'},
974 size => $fdata->{'size'},
975 default_value => $fdata->{'default'},
976 is_auto_increment => $fdata->{'is_auto_inc'},
977 is_nullable => $fdata->{'null'},
88b89793 978 comments => $fdata->{'comments'},
40c1ade1 979 ) or die $table->error;
f2cf1734 980
981 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
982
bd30a9a2 983 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 984 'character set', 'on update' ) {
f2cf1734 985 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
986 next if ref $val eq 'ARRAY' && !@$val;
987 $field->extra( $qual, $val );
988 }
989 }
990
bd356af8 991 if ( $fdata->{'has_index'} ) {
992 $table->add_index(
993 name => '',
994 type => 'NORMAL',
995 fields => $fdata->{'name'},
996 ) or die $table->error;
997 }
998
999 if ( $fdata->{'is_unique'} ) {
1000 $table->add_constraint(
1001 name => '',
1002 type => 'UNIQUE',
1003 fields => $fdata->{'name'},
1004 ) or die $table->error;
1005 }
1006
f2cf1734 1007 for my $cdata ( @{ $fdata->{'constraints'} } ) {
1008 next unless $cdata->{'type'} eq 'foreign_key';
1009 $cdata->{'fields'} ||= [ $field->name ];
1010 push @{ $tdata->{'constraints'} }, $cdata;
1011 }
07d6e5f7 1012
f2cf1734 1013 }
1014
1015 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
1016 my $index = $table->add_index(
1017 name => $idata->{'name'},
1018 type => uc $idata->{'type'},
1019 fields => $idata->{'fields'},
1020 ) or die $table->error;
1021 }
1022
02a21f1a 1023 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
f7f81963 1024 my @cleaned_options;
f1fe509d 1025 my @ignore_opts = $translator->parser_args->{'ignore_opts'}
1026 ? split( /,/, $translator->parser_args->{'ignore_opts'} )
1027 : ();
f7f81963 1028 if (@ignore_opts) {
1029 my $ignores = { map { $_ => 1 } @ignore_opts };
1030 foreach my $option (@options) {
1031 # make sure the option isn't in ignore list
1032 my ($option_key) = keys %$option;
f1fe509d 1033 if ( !exists $ignores->{$option_key} ) {
1034 push @cleaned_options, $option;
1035 }
f7f81963 1036 }
1037 } else {
1038 @cleaned_options = @options;
1039 }
1040 $table->options( \@cleaned_options ) or die $table->error;
02a21f1a 1041 }
1042
f2cf1734 1043 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
1044 my $constraint = $table->add_constraint(
1045 name => $cdata->{'name'},
1046 type => $cdata->{'type'},
1047 fields => $cdata->{'fields'},
51fe887d 1048 expression => $cdata->{'expression'},
f2cf1734 1049 reference_table => $cdata->{'reference_table'},
1050 reference_fields => $cdata->{'reference_fields'},
1051 match_type => $cdata->{'match_type'} || '',
ea93df61 1052 on_delete => $cdata->{'on_delete'}
f1fe509d 1053 || $cdata->{'on_delete_do'},
ea93df61 1054 on_update => $cdata->{'on_update'}
f1fe509d 1055 || $cdata->{'on_update_do'},
f2cf1734 1056 ) or die $table->error;
8ccdeb42 1057 }
07d6e5f7 1058
ea93df61 1059 # After the constrains and PK/idxs have been created,
f1fe509d 1060 # we normalize fields
07d6e5f7 1061 normalize_field($_) for $table->get_fields;
8ccdeb42 1062 }
ea93df61 1063
1064 my @procedures = sort {
1065 $result->{procedures}->{ $a }->{'order'}
1066 <=>
f1fe509d 1067 $result->{procedures}->{ $b }->{'order'}
d31c185b 1068 } keys %{ $result->{procedures} };
f1fe509d 1069
1070 for my $proc_name ( @procedures ) {
07d6e5f7 1071 $schema->add_procedure(
1072 name => $proc_name,
1073 owner => $result->{procedures}->{$proc_name}->{owner},
1074 sql => $result->{procedures}->{$proc_name}->{sql},
1075 );
d31c185b 1076 }
aa4301a7 1077
ea93df61 1078 my @views = sort {
1079 $result->{views}->{ $a }->{'order'}
1080 <=>
f1fe509d 1081 $result->{views}->{ $b }->{'order'}
d31c185b 1082 } keys %{ $result->{views} };
f1fe509d 1083
3ebe2ce5 1084 for my $view_name ( @views ) {
aa4301a7 1085 my $view = $result->{'views'}{ $view_name };
1086 my @flds = map { $_->{'alias'} || $_->{'name'} }
1087 @{ $view->{'select'}{'columns'} || [] };
e6c5fb6e 1088 my @from = map { $_->{'alias'} || $_->{'name'} }
1089 @{ $view->{'from'}{'tables'} || [] };
aa4301a7 1090
07d6e5f7 1091 $schema->add_view(
aa4301a7 1092 name => $view_name,
1093 sql => $view->{'sql'},
1094 order => $view->{'order'},
1095 fields => \@flds,
e6c5fb6e 1096 tables => \@from,
1097 options => $view->{'options'}
07d6e5f7 1098 );
d31c185b 1099 }
8ccdeb42 1100
f62bd16c 1101 return 1;
d529894e 1102}
1103
ea93df61 1104# Takes a field, and returns
07d6e5f7 1105sub normalize_field {
1106 my ($field) = @_;
936e626b 1107 my ($size, $type, $list, $unsigned, $changed);
ea93df61 1108
07d6e5f7 1109 $size = $field->size;
1110 $type = $field->data_type;
1111 $list = $field->extra->{list} || [];
936e626b 1112 $unsigned = defined($field->extra->{unsigned});
07d6e5f7 1113
1114 if ( !ref $size && $size eq 0 ) {
1115 if ( lc $type eq 'tinyint' ) {
936e626b 1116 $changed = $size != 4 - $unsigned;
1117 $size = 4 - $unsigned;
07d6e5f7 1118 }
1119 elsif ( lc $type eq 'smallint' ) {
936e626b 1120 $changed = $size != 6 - $unsigned;
1121 $size = 6 - $unsigned;
07d6e5f7 1122 }
1123 elsif ( lc $type eq 'mediumint' ) {
936e626b 1124 $changed = $size != 9 - $unsigned;
1125 $size = 9 - $unsigned;
07d6e5f7 1126 }
1127 elsif ( $type =~ /^int(eger)?$/i ) {
936e626b 1128 $changed = $size != 11 - $unsigned || $type ne 'int';
07d6e5f7 1129 $type = 'int';
936e626b 1130 $size = 11 - $unsigned;
07d6e5f7 1131 }
1132 elsif ( lc $type eq 'bigint' ) {
1133 $changed = $size != 20;
1134 $size = 20;
1135 }
1136 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
1137 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
ea93df61 1138 $changed = @$old_size != 2
1139 || $old_size->[0] != 8
f1fe509d 1140 || $old_size->[1] != 2;
1141 $size = [8,2];
07d6e5f7 1142 }
1143 }
1144
1145 if ( $type =~ /^tiny(text|blob)$/i ) {
1146 $changed = $size != 255;
1147 $size = 255;
1148 }
1149 elsif ( $type =~ /^(blob|text)$/i ) {
1150 $changed = $size != 65_535;
1151 $size = 65_535;
1152 }
1153 elsif ( $type =~ /^medium(blob|text)$/i ) {
1154 $changed = $size != 16_777_215;
1155 $size = 16_777_215;
1156 }
1157 elsif ( $type =~ /^long(blob|text)$/i ) {
1158 $changed = $size != 4_294_967_295;
1159 $size = 4_294_967_295;
1160 }
f1fe509d 1161
9ab59f87 1162 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
1163 my %extra = $field->extra;
1164 my $longest = 0;
1165 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
1166 $longest = $len if $len > $longest;
1167 }
1168 $changed = 1;
1169 $size = $longest if $longest;
1170 }
1171
1172
f1fe509d 1173 if ( $changed ) {
1174 # We only want to clone the field, not *everything*
1175 {
1176 local $field->{table} = undef;
1177 $field->parsed_field( dclone( $field ) );
1178 $field->parsed_field->{table} = $field->table;
1179 }
1180 $field->size( $size );
1181 $field->data_type( $type );
1182 $field->sql_data_type( $type_mapping{ lc $type } )
1183 if exists $type_mapping{ lc $type };
1184 $field->extra->{list} = $list if @$list;
07d6e5f7 1185 }
1186}
1187
d529894e 11881;
1189
034ecdec 1190# -------------------------------------------------------------------
d529894e 1191# Where man is not nature is barren.
1192# William Blake
034ecdec 1193# -------------------------------------------------------------------
16dc9970 1194
d529894e 1195=pod
16dc9970 1196
1197=head1 AUTHOR
1198
19c5bc53 1199Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 1200Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 1201
1202=head1 SEE ALSO
1203
19c5bc53 1204Parse::RecDescent, SQL::Translator::Schema.
16dc9970 1205
1206=cut