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