Reduce $Id to its normal form
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
782b5a43 4# $Id$
49e1eb70 5# -------------------------------------------------------------------
478f608d 6# Copyright (C) 2002-2009 SQLFairy Authors
077ebf34 7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License as
10# published by the Free Software Foundation; version 2.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20# 02111-1307 USA
21# -------------------------------------------------------------------
16dc9970 22
d529894e 23=head1 NAME
24
25SQL::Translator::Parser::MySQL - parser for MySQL
26
27=head1 SYNOPSIS
28
29 use SQL::Translator;
30 use SQL::Translator::Parser::MySQL;
31
32 my $translator = SQL::Translator->new;
33 $translator->parser("SQL::Translator::Parser::MySQL");
34
35=head1 DESCRIPTION
36
37The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
38
629b76f9 39Here's the word from the MySQL site
40(http://www.mysql.com/doc/en/CREATE_TABLE.html):
41
42 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
43 [table_options] [select_statement]
44
45 or
46
47 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;
48
49 create_definition:
50 col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
51 [PRIMARY KEY] [reference_definition]
52 or PRIMARY KEY (index_col_name,...)
53 or KEY [index_name] (index_col_name,...)
54 or INDEX [index_name] (index_col_name,...)
55 or UNIQUE [INDEX] [index_name] (index_col_name,...)
56 or FULLTEXT [INDEX] [index_name] (index_col_name,...)
57 or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
58 [reference_definition]
59 or CHECK (expr)
60
61 type:
62 TINYINT[(length)] [UNSIGNED] [ZEROFILL]
63 or SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
64 or MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
65 or INT[(length)] [UNSIGNED] [ZEROFILL]
66 or INTEGER[(length)] [UNSIGNED] [ZEROFILL]
67 or BIGINT[(length)] [UNSIGNED] [ZEROFILL]
68 or REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
69 or DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
70 or FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
71 or DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
72 or NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
73 or CHAR(length) [BINARY]
74 or VARCHAR(length) [BINARY]
75 or DATE
76 or TIME
77 or TIMESTAMP
78 or DATETIME
79 or TINYBLOB
80 or BLOB
81 or MEDIUMBLOB
82 or LONGBLOB
83 or TINYTEXT
84 or TEXT
85 or MEDIUMTEXT
86 or LONGTEXT
87 or ENUM(value1,value2,value3,...)
88 or SET(value1,value2,value3,...)
89
90 index_col_name:
91 col_name [(length)]
92
93 reference_definition:
94 REFERENCES tbl_name [(index_col_name,...)]
95 [MATCH FULL | MATCH PARTIAL]
96 [ON DELETE reference_option]
97 [ON UPDATE reference_option]
98
99 reference_option:
100 RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
101
102 table_options:
103 TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
a7f49dfb 104 or ENGINE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
629b76f9 105 or AUTO_INCREMENT = #
106 or AVG_ROW_LENGTH = #
a7f49dfb 107 or [ DEFAULT ] CHARACTER SET charset_name
629b76f9 108 or CHECKSUM = {0 | 1}
a7f49dfb 109 or COLLATE collation_name
629b76f9 110 or COMMENT = "string"
111 or MAX_ROWS = #
112 or MIN_ROWS = #
113 or PACK_KEYS = {0 | 1 | DEFAULT}
114 or PASSWORD = "string"
115 or DELAY_KEY_WRITE = {0 | 1}
116 or ROW_FORMAT= { default | dynamic | fixed | compressed }
117 or RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=# RAID_CHUNKSIZE=#
118 or UNION = (table_name,[table_name...])
119 or INSERT_METHOD= {NO | FIRST | LAST }
120 or DATA DIRECTORY="absolute path to directory"
121 or INDEX DIRECTORY="absolute path to directory"
122
a7f49dfb 123
13aec984 124A subset of the ALTER TABLE syntax that allows addition of foreign keys:
125
126 ALTER [IGNORE] TABLE tbl_name alter_specification [, alter_specification] ...
127
128 alter_specification:
129 ADD [CONSTRAINT [symbol]]
130 FOREIGN KEY [index_name] (index_col_name,...)
131 [reference_definition]
132
133A subset of INSERT that we ignore:
134
135 INSERT anything
136
5d666b31 137=head1 ARGUMENTS
138
139This parser takes a single optional parser_arg C<mysql_parser_version>, which
140provides the desired version for the target database. Any statement in the processed
141dump file, that is commented with a version higher than the one supplied, will be stripped.
142
143Valid version specifiers for C<mysql_parser_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
144
145More information about the MySQL comment-syntax: L<http://dev.mysql.com/doc/refman/5.0/en/comments.html>
146
147
d529894e 148=cut
149
16dc9970 150use strict;
da06ac74 151use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
152$VERSION = '1.99';
8d0f3086 153$DEBUG = 0 unless defined $DEBUG;
077ebf34 154
d529894e 155use Data::Dumper;
077ebf34 156use Parse::RecDescent;
157use Exporter;
07d6e5f7 158use Storable qw(dclone);
6b2dbb1a 159use DBI qw(:sql_types);
077ebf34 160use base qw(Exporter);
161
5d666b31 162use SQL::Translator::Utils qw/parse_mysql_version/;
163
6b2dbb1a 164our %type_mapping = (
6b2dbb1a 165);
166
077ebf34 167@EXPORT_OK = qw(parse);
168
d529894e 169# Enable warnings within the Parse::RecDescent module.
170$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
171$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
172$::RD_HINT = 1; # Give out hints to help fix problems.
173
d31c185b 174use constant DEFAULT_PARSER_VERSION => 30000;
175
9bf756df 176$GRAMMAR = << 'END_OF_GRAMMAR';
d529894e 177
8ccdeb42 178{
d31c185b 179 my ( $database_name, %tables, $table_order, @table_comments, %views, $view_order, %procedures, $proc_order );
86318717 180 my $delimiter = ';';
8ccdeb42 181}
d529894e 182
629b76f9 183#
184# The "eofile" rule makes the parser fail if any "statement" rule
185# fails. Otherwise, the first successful match by a "statement"
186# won't cause the failure needed to know that the parse, as a whole,
187# failed. -ky
188#
13aec984 189startrule : statement(s) eofile {
d31c185b 190 { tables => \%tables, database_name => $database_name, views => \%views, procedures =>\%procedures }
13aec984 191}
629b76f9 192
193eofile : /^\Z/
d529894e 194
195statement : comment
dcb4fa06 196 | use
33d0d6d4 197 | set
61745327 198 | drop
d529894e 199 | create
13aec984 200 | alter
201 | insert
86318717 202 | delimiter
203 | empty_statement
d529894e 204 | <error>
205
86318717 206use : /use/i WORD "$delimiter"
13aec984 207 {
208 $database_name = $item[2];
209 @table_comments = ();
210 }
dcb4fa06 211
86318717 212set : /set/i /[^;]+/ "$delimiter"
c5dabd71 213 { @table_comments = () }
734dfc91 214
86318717 215drop : /drop/i TABLE /[^;]+/ "$delimiter"
33d0d6d4 216
86318717 217drop : /drop/i WORD(s) "$delimiter"
c5dabd71 218 { @table_comments = () }
61745327 219
9bf756df 220string :
221 # MySQL strings, unlike common SQL strings, can be double-quoted or
222 # single-quoted, and you can escape the delmiters by doubling (but only the
223 # delimiter) or by backslashing.
224
225 /'(\\.|''|[^\\\'])*'/ |
226 /"(\\.|""|[^\\\"])*"/
227 # For reference, std sql str: /(?:(?:\')(?:[^\']*(?:(?:\'\')[^\']*)*)(?:\'))//
228
229nonstring : /[^;\'"]+/
230
231statement_body : (string | nonstring)(s?)
232
86318717 233insert : /insert/i statement_body "$delimiter"
13aec984 234
86318717 235delimiter : /delimiter/i /[\S]+/
07d6e5f7 236 { $delimiter = $item[2] }
86318717 237
238empty_statement : "$delimiter"
239
240alter : ALTER TABLE table_name alter_specification(s /,/) "$delimiter"
13aec984 241 {
242 my $table_name = $item{'table_name'};
243 die "Cannot ALTER table '$table_name'; it does not exist"
244 unless $tables{ $table_name };
245 for my $definition ( @{ $item[4] } ) {
246 $definition->{'extra'}->{'alter'} = 1;
247 push @{ $tables{ $table_name }{'constraints'} }, $definition;
248 }
249 }
250
251alter_specification : ADD foreign_key_def
252 { $return = $item[2] }
253
86318717 254create : CREATE /database/i WORD "$delimiter"
c5dabd71 255 { @table_comments = () }
dcb4fa06 256
86318717 257create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) "$delimiter"
d529894e 258 {
259 my $table_name = $item{'table_name'};
260 $tables{ $table_name }{'order'} = ++$table_order;
261 $tables{ $table_name }{'table_name'} = $table_name;
262
734dfc91 263 if ( @table_comments ) {
264 $tables{ $table_name }{'comments'} = [ @table_comments ];
265 @table_comments = ();
266 }
267
61745327 268 my $i = 1;
40c1ade1 269 for my $definition ( @{ $item[7] } ) {
f2cf1734 270 if ( $definition->{'supertype'} eq 'field' ) {
d529894e 271 my $field_name = $definition->{'name'};
272 $tables{ $table_name }{'fields'}{ $field_name } =
273 { %$definition, order => $i };
274 $i++;
275
276 if ( $definition->{'is_primary_key'} ) {
f2cf1734 277 push @{ $tables{ $table_name }{'constraints'} },
d529894e 278 {
279 type => 'primary_key',
280 fields => [ $field_name ],
16dc9970 281 }
d529894e 282 ;
283 }
dd2ef5ae 284 }
f2cf1734 285 elsif ( $definition->{'supertype'} eq 'constraint' ) {
f2cf1734 286 push @{ $tables{ $table_name }{'constraints'} }, $definition;
40c1ade1 287 }
f2cf1734 288 elsif ( $definition->{'supertype'} eq 'index' ) {
734dfc91 289 push @{ $tables{ $table_name }{'indices'} }, $definition;
dd2ef5ae 290 }
d529894e 291 }
dd2ef5ae 292
02a21f1a 293 if ( my @options = @{ $item{'table_option(s?)'} } ) {
35843e6b 294 for my $option ( @options ) {
295 my ( $key, $value ) = each %$option;
296 if ( $key eq 'comment' ) {
297 push @{ $tables{ $table_name }{'comments'} }, $value;
298 }
299 else {
300 push @{ $tables{ $table_name }{'table_options'} }, $option;
301 }
302 }
d529894e 303 }
58a88238 304
305 1;
d529894e 306 }
dd2ef5ae 307
40c1ade1 308opt_if_not_exists : /if not exists/i
309
86318717 310create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' "$delimiter"
d529894e 311 {
734dfc91 312 @table_comments = ();
d529894e 313 push @{ $tables{ $item{'table_name'} }{'indices'} },
314 {
315 name => $item[4],
041e659f 316 type => $item[2][0] ? 'unique' : 'normal',
d529894e 317 fields => $item[8],
dd2ef5ae 318 }
d529894e 319 ;
320 }
dd2ef5ae 321
d31c185b 322create : CREATE /trigger/i NAME not_delimiter "$delimiter"
07d6e5f7 323 {
324 @table_comments = ();
325 }
d31c185b 326
327create : CREATE PROCEDURE NAME not_delimiter "$delimiter"
07d6e5f7 328 {
329 @table_comments = ();
d31c185b 330 my $func_name = $item[3];
331 my $owner = '';
332 my $sql = "$item[1] $item[2] $item[3] $item[4]";
333
334 $procedures{ $func_name }{'order'} = ++$proc_order;
335 $procedures{ $func_name }{'name'} = $func_name;
336 $procedures{ $func_name }{'owner'} = $owner;
337 $procedures{ $func_name }{'sql'} = $sql;
07d6e5f7 338 }
d31c185b 339
340PROCEDURE : /procedure/i
07d6e5f7 341 | /function/i
d31c185b 342
343create : CREATE algorithm /view/i NAME not_delimiter "$delimiter"
07d6e5f7 344 {
345 @table_comments = ();
d31c185b 346 my $view_name = $item[4];
347 my $sql = "$item[1] $item[2] $item[3] $item[4] $item[5]";
348
349 # Hack to strip database from function calls in SQL
350 $sql =~ s#`\w+`\.(`\w+`\()##g;
351
352 $views{ $view_name }{'order'} = ++$view_order;
353 $views{ $view_name }{'name'} = $view_name;
354 $views{ $view_name }{'sql'} = $sql;
07d6e5f7 355 }
d31c185b 356
357algorithm : /algorithm/i /=/ WORD
07d6e5f7 358 {
359 $return = "$item[1]=$item[3]";
360 }
d31c185b 361
362not_delimiter : /.*?(?=$delimiter)/is
363
f2cf1734 364create_definition : constraint
365 | index
d529894e 366 | field
02a21f1a 367 | comment
d529894e 368 | <error>
369
734dfc91 370comment : /^\s*(?:#|-{2}).*\n/
371 {
372 my $comment = $item[1];
a82fa2cb 373 $comment =~ s/^\s*(#|--)\s*//;
734dfc91 374 $comment =~ s/\s*$//;
375 $return = $comment;
734dfc91 376 }
377
da9f2af8 378comment : /\/\*/ /.*?\*\//s
e78d62f2 379 {
380 my $comment = $item[2];
73212389 381 $comment = substr($comment, 0, -2);
e78d62f2 382 $comment =~ s/^\s*|\s*$//g;
383 $return = $comment;
384 }
86318717 385
734dfc91 386field_comment : /^\s*(?:#|-{2}).*\n/
387 {
388 my $comment = $item[1];
a82fa2cb 389 $comment =~ s/^\s*(#|--)\s*//;
734dfc91 390 $comment =~ s/\s*$//;
391 $return = $comment;
392 }
d529894e 393
35843e6b 394
395field_comment2 : /comment/i /'.*?'/
396 {
397 my $comment = $item[2];
398 $comment =~ s/^'//;
399 $comment =~ s/'$//;
400 $return = $comment;
401 }
402
d529894e 403blank : /\s*/
404
100684f3 405field : field_comment(s?) field_name data_type field_qualifier(s?) field_comment2(?) reference_definition(?) on_update(?) field_comment(s?)
d529894e 406 {
734dfc91 407 my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
d529894e 408 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
409 $qualifiers{ $_ } = 1 for @type_quals;
410 }
411
c5dabd71 412 my $null = defined $qualifiers{'not_null'}
413 ? $qualifiers{'not_null'} : 1;
414 delete $qualifiers{'not_null'};
415
35843e6b 416 my @comments = ( @{ $item[1] }, @{ $item[5] }, @{ $item[8] } );
88b89793 417
d529894e 418 $return = {
f2cf1734 419 supertype => 'field',
420 name => $item{'field_name'},
421 data_type => $item{'data_type'}{'type'},
422 size => $item{'data_type'}{'size'},
423 list => $item{'data_type'}{'list'},
424 null => $null,
425 constraints => $item{'reference_definition(?)'},
88b89793 426 comments => [ @comments ],
d529894e 427 %qualifiers,
428 }
429 }
430 | <error>
dd2ef5ae 431
d529894e 432field_qualifier : not_null
433 {
434 $return = {
435 null => $item{'not_null'},
436 }
437 }
16dc9970 438
d529894e 439field_qualifier : default_val
440 {
441 $return = {
442 default => $item{'default_val'},
443 }
444 }
16dc9970 445
d529894e 446field_qualifier : auto_inc
447 {
448 $return = {
449 is_auto_inc => $item{'auto_inc'},
450 }
451 }
16dc9970 452
d529894e 453field_qualifier : primary_key
454 {
455 $return = {
456 is_primary_key => $item{'primary_key'},
457 }
458 }
16dc9970 459
d529894e 460field_qualifier : unsigned
461 {
462 $return = {
463 is_unsigned => $item{'unsigned'},
464 }
465 }
16dc9970 466
19c5bc53 467field_qualifier : /character set/i WORD
095b4549 468 {
469 $return = {
bd30a9a2 470 'CHARACTER SET' => $item[2],
471 }
472 }
473
474field_qualifier : /collate/i WORD
475 {
476 $return = {
477 COLLATE => $item[2],
478 }
479 }
480
481field_qualifier : /on update/i CURRENT_TIMESTAMP
482 {
483 $return = {
484 'ON UPDATE' => $item[2],
095b4549 485 }
486 }
487
bd356af8 488field_qualifier : /unique/i KEY(?)
489 {
490 $return = {
491 is_unique => 1,
492 }
493 }
494
495field_qualifier : KEY
496 {
497 $return = {
498 has_index => 1,
499 }
500 }
501
100684f3 502reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
658637cd 503 {
40c1ade1 504 $return = {
658637cd 505 type => 'foreign_key',
506 reference_table => $item[2],
507 reference_fields => $item[3][0],
508 match_type => $item[4][0],
100684f3 509 on_delete => $item[5][0],
510 on_update => $item[6][0],
658637cd 511 }
512 }
513
02a21f1a 514match_type : /match full/i { 'full' }
658637cd 515 |
02a21f1a 516 /match partial/i { 'partial' }
658637cd 517
100684f3 518on_delete : /on delete/i reference_option
658637cd 519 { $item[2] }
520
100684f3 521on_update :
6fa97af6 522 /on update/i 'CURRENT_TIMESTAMP'
523 { $item[2] }
524 |
525 /on update/i reference_option
658637cd 526 { $item[2] }
527
528reference_option: /restrict/i |
529 /cascade/i |
530 /set null/i |
531 /no action/i |
532 /set default/i
533 { $item[1] }
534
f2cf1734 535index : normal_index
371f5f88 536 | fulltext_index
531652d6 537 | spatial_index
58a88238 538 | <error>
d529894e 539
0d41bc9b 540table_name : NAME
d529894e 541
0d41bc9b 542field_name : NAME
d529894e 543
02a21f1a 544index_name : NAME
d529894e 545
546data_type : WORD parens_value_list(s?) type_qualifier(s?)
547 {
548 my $type = $item[1];
549 my $size; # field size, applicable only to non-set fields
550 my $list; # set list, applicable only to sets (duh)
551
44fcd0b5 552 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 553 $size = undef;
554 $list = $item[2][0];
555 }
556 else {
557 $size = $item[2][0];
558 $list = [];
559 }
560
256d534a 561
d529894e 562 $return = {
563 type => $type,
564 size => $size,
565 list => $list,
566 qualifiers => $item[3],
567 }
568 }
16dc9970 569
658637cd 570parens_field_list : '(' field_name(s /,/) ')'
571 { $item[2] }
572
d529894e 573parens_value_list : '(' VALUE(s /,/) ')'
574 { $item[2] }
16dc9970 575
d529894e 576type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
577 { lc $item[1] }
16dc9970 578
d529894e 579field_type : WORD
16dc9970 580
d529894e 581create_index : /create/i /index/i
dd2ef5ae 582
e78d62f2 583not_null : /not/i /null/i
584 { $return = 0 }
585 |
586 /null/i
587 { $return = 1 }
16dc9970 588
d529894e 589unsigned : /unsigned/i { $return = 0 }
16dc9970 590
09fa21a6 591#default_val : /default/i /(?:')?[\s\w\d:.-]*(?:')?/
592# {
593# $item[2] =~ s/'//g;
594# $return = $item[2];
595# }
596
6fa97af6 597default_val :
598 /default/i 'CURRENT_TIMESTAMP'
599 {
0f4c1a5b 600 $return = \$item[2];
6fa97af6 601 }
602 |
603 /default/i /'(?:.*?\\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
09fa21a6 604 {
605 $item[2] =~ s/^\s*'|'\s*$//g;
d529894e 606 $return = $item[2];
607 }
16dc9970 608
d529894e 609auto_inc : /auto_increment/i { 1 }
16dc9970 610
d529894e 611primary_key : /primary/i /key/i { 1 }
16dc9970 612
f2cf1734 613constraint : primary_key_def
614 | unique_key_def
615 | foreign_key_def
616 | <error>
617
02a21f1a 618foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
40c1ade1 619 {
620 $return = {
f2cf1734 621 supertype => 'constraint',
40c1ade1 622 type => 'foreign_key',
02a21f1a 623 name => $item[1],
09fa21a6 624 fields => $item[2],
40c1ade1 625 %{ $item{'reference_definition'} },
626 }
627 }
628
e78d62f2 629foreign_key_def_begin : /constraint/i /foreign key/i WORD
630 { $return = $item[3] }
631 |
632 /constraint/i NAME /foreign key/i
633 { $return = $item[2] }
634 |
635 /constraint/i /foreign key/i
02a21f1a 636 { $return = '' }
637 |
e78d62f2 638 /foreign key/i WORD
02a21f1a 639 { $return = $item[2] }
640 |
641 /foreign key/i
642 { $return = '' }
40c1ade1 643
1853ba82 644primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 645 {
f2cf1734 646 $return = {
647 supertype => 'constraint',
648 name => $item{'index_name(?)'}[0],
649 type => 'primary_key',
650 fields => $item[4],
58a88238 651 };
d529894e 652 }
16dc9970 653
f2cf1734 654unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 655 {
f2cf1734 656 $return = {
657 supertype => 'constraint',
658 name => $item{'index_name(?)'}[0],
659 type => 'unique',
660 fields => $item[5],
d529894e 661 }
662 }
16dc9970 663
f2cf1734 664normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 665 {
f2cf1734 666 $return = {
667 supertype => 'index',
668 type => 'normal',
669 name => $item{'index_name(?)'}[0],
670 fields => $item[4],
d529894e 671 }
672 }
16dc9970 673
f2cf1734 674fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
371f5f88 675 {
f2cf1734 676 $return = {
677 supertype => 'index',
678 type => 'fulltext',
679 name => $item{'index_name(?)'}[0],
680 fields => $item[5],
371f5f88 681 }
682 }
683
531652d6 684spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
685 {
686 $return = {
687 supertype => 'index',
688 type => 'spatial',
689 name => $item{'index_name(?)'}[0],
690 fields => $item[5],
691 }
692 }
693
d529894e 694name_with_opt_paren : NAME parens_value_list(s?)
695 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 696
041e659f 697UNIQUE : /unique/i
16dc9970 698
f2cf1734 699KEY : /key/i | /index/i
16dc9970 700
19c5bc53 701table_option : /comment/i /=/ /'.*?'/
35843e6b 702 {
703 my $comment = $item[3];
704 $comment =~ s/^'//;
705 $comment =~ s/'$//;
706 $return = { comment => $comment };
707 }
bb4c66d1 708 | /(default )?(charset|character set)/i /\s*=?\s*/ WORD
d529894e 709 {
bd30a9a2 710 $return = { 'CHARACTER SET' => $item[3] };
d529894e 711 }
a7f49dfb 712 | /collate/i WORD
713 {
714 $return = { 'COLLATE' => $item[2] }
715 }
9a96648f 716 | /union/i /\s*=\s*/ '(' table_name(s /,/) ')'
717 {
718 $return = { $item[1] => $item[4] };
719 }
19c5bc53 720 | WORD /\s*=\s*/ WORD
721 {
722 $return = { $item[1] => $item[3] };
723 }
bd30a9a2 724
725default : /default/i
16dc9970 726
13aec984 727ADD : /add/i
728
729ALTER : /alter/i
730
40c1ade1 731CREATE : /create/i
732
733TEMPORARY : /temporary/i
734
735TABLE : /table/i
736
d529894e 737WORD : /\w+/
16dc9970 738
d529894e 739DIGITS : /\d+/
16dc9970 740
d529894e 741COMMA : ','
16dc9970 742
a7f49dfb 743BACKTICK : '`'
744
531652d6 745NAME : BACKTICK /[^`]+/ BACKTICK
d529894e 746 { $item[2] }
747 | /\w+/
748 { $item[1] }
16dc9970 749
d529894e 750VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
751 { $item[1] }
f2cf1734 752 | /'.*?'/
753 {
754 # remove leading/trailing quotes
755 my $val = $item[1];
756 $val =~ s/^['"]|['"]$//g;
757 $return = $val;
758 }
d529894e 759 | /NULL/
760 { 'NULL' }
16dc9970 761
bd30a9a2 762CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
07d6e5f7 763 | /now\(\)/i
764 { 'CURRENT_TIMESTAMP' }
765
9bf756df 766END_OF_GRAMMAR
16dc9970 767
d529894e 768# -------------------------------------------------------------------
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 }
d31c185b 779
780 # Preprocess for MySQL-specific and not-before-version comments from mysqldump
5d666b31 781 my $parser_version =
782 parse_mysql_version ($translator->parser_args->{mysql_parser_version}, 'mysql')
783 || DEFAULT_PARSER_VERSION;
d31c185b 784 while ( $data =~ s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es ) {}
d529894e 785
786 my $result = $parser->startrule($data);
40c1ade1 787 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 788 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 789
70944bc5 790 my $schema = $translator->schema;
13aec984 791 $schema->name($result->{'database_name'}) if $result->{'database_name'};
792
034ecdec 793 my @tables = sort {
13aec984 794 $result->{'tables'}{ $a }{'order'}
795 <=>
796 $result->{'tables'}{ $b }{'order'}
797 } keys %{ $result->{'tables'} };
034ecdec 798
799 for my $table_name ( @tables ) {
13aec984 800 my $tdata = $result->{tables}{ $table_name };
8ccdeb42 801 my $table = $schema->add_table(
802 name => $tdata->{'table_name'},
40c1ade1 803 ) or die $schema->error;
8ccdeb42 804
734dfc91 805 $table->comments( $tdata->{'comments'} );
f2cf1734 806
8ccdeb42 807 my @fields = sort {
808 $tdata->{'fields'}->{$a}->{'order'}
809 <=>
810 $tdata->{'fields'}->{$b}->{'order'}
811 } keys %{ $tdata->{'fields'} };
812
813 for my $fname ( @fields ) {
814 my $fdata = $tdata->{'fields'}{ $fname };
815 my $field = $table->add_field(
816 name => $fdata->{'name'},
817 data_type => $fdata->{'data_type'},
818 size => $fdata->{'size'},
819 default_value => $fdata->{'default'},
820 is_auto_increment => $fdata->{'is_auto_inc'},
821 is_nullable => $fdata->{'null'},
88b89793 822 comments => $fdata->{'comments'},
40c1ade1 823 ) or die $table->error;
f2cf1734 824
825 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
826
bd30a9a2 827 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 828 'character set', 'on update' ) {
f2cf1734 829 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
830 next if ref $val eq 'ARRAY' && !@$val;
831 $field->extra( $qual, $val );
832 }
833 }
834
bd356af8 835 if ( $fdata->{'has_index'} ) {
836 $table->add_index(
837 name => '',
838 type => 'NORMAL',
839 fields => $fdata->{'name'},
840 ) or die $table->error;
841 }
842
843 if ( $fdata->{'is_unique'} ) {
844 $table->add_constraint(
845 name => '',
846 type => 'UNIQUE',
847 fields => $fdata->{'name'},
848 ) or die $table->error;
849 }
850
f2cf1734 851 for my $cdata ( @{ $fdata->{'constraints'} } ) {
852 next unless $cdata->{'type'} eq 'foreign_key';
853 $cdata->{'fields'} ||= [ $field->name ];
854 push @{ $tdata->{'constraints'} }, $cdata;
855 }
07d6e5f7 856
f2cf1734 857 }
858
859 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
860 my $index = $table->add_index(
861 name => $idata->{'name'},
862 type => uc $idata->{'type'},
863 fields => $idata->{'fields'},
864 ) or die $table->error;
865 }
866
02a21f1a 867 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
f7f81963 868 my @cleaned_options;
869 my @ignore_opts = $translator->parser_args->{ignore_opts}?split(/,/,$translator->parser_args->{ignore_opts}):();
870 if (@ignore_opts) {
871 my $ignores = { map { $_ => 1 } @ignore_opts };
872 foreach my $option (@options) {
873 # make sure the option isn't in ignore list
874 my ($option_key) = keys %$option;
875 push(@cleaned_options, $option) unless (exists $ignores->{$option_key});
876 }
877 } else {
878 @cleaned_options = @options;
879 }
880 $table->options( \@cleaned_options ) or die $table->error;
02a21f1a 881 }
882
f2cf1734 883 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
884 my $constraint = $table->add_constraint(
885 name => $cdata->{'name'},
886 type => $cdata->{'type'},
887 fields => $cdata->{'fields'},
888 reference_table => $cdata->{'reference_table'},
889 reference_fields => $cdata->{'reference_fields'},
890 match_type => $cdata->{'match_type'} || '',
100684f3 891 on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
892 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
f2cf1734 893 ) or die $table->error;
8ccdeb42 894 }
07d6e5f7 895
896 # After the constrains and PK/idxs have been created, we normalize fields
897 normalize_field($_) for $table->get_fields;
8ccdeb42 898 }
d31c185b 899
900 my @procedures = sort {
901 $result->{procedures}->{ $a }->{'order'} <=> $result->{procedures}->{ $b }->{'order'}
902 } keys %{ $result->{procedures} };
903 foreach my $proc_name (@procedures) {
07d6e5f7 904 $schema->add_procedure(
905 name => $proc_name,
906 owner => $result->{procedures}->{$proc_name}->{owner},
907 sql => $result->{procedures}->{$proc_name}->{sql},
908 );
d31c185b 909 }
910
911 my @views = sort {
912 $result->{views}->{ $a }->{'order'} <=> $result->{views}->{ $b }->{'order'}
913 } keys %{ $result->{views} };
914 foreach my $view_name (keys %{ $result->{views} }) {
07d6e5f7 915 $schema->add_view(
916 name => $view_name,
917 sql => $result->{views}->{$view_name}->{sql},
918 );
d31c185b 919 }
8ccdeb42 920
f62bd16c 921 return 1;
d529894e 922}
923
07d6e5f7 924# Takes a field, and returns
925sub normalize_field {
926 my ($field) = @_;
927 my ($size, $type, $list, $changed) = @_;
928
929 $size = $field->size;
930 $type = $field->data_type;
931 $list = $field->extra->{list} || [];
932
933 if ( !ref $size && $size eq 0 ) {
934 if ( lc $type eq 'tinyint' ) {
935 $changed = $size != 4;
936 $size = 4;
937 }
938 elsif ( lc $type eq 'smallint' ) {
939 $changed = $size != 6;
940 $size = 6;
941 }
942 elsif ( lc $type eq 'mediumint' ) {
943 $changed = $size != 9;
944 $size = 9;
945 }
946 elsif ( $type =~ /^int(eger)?$/i ) {
947 $changed = $size != 11 || $type ne 'int';
948 $type = 'int';
949 $size = 11;
950 }
951 elsif ( lc $type eq 'bigint' ) {
952 $changed = $size != 20;
953 $size = 20;
954 }
955 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
956 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
54b6e490 957 $changed = @$old_size != 2 || $old_size->[0] != 8 || $old_size->[1] != 2;
07d6e5f7 958 $size = [8,2];
959 }
960 }
961
962 if ( $type =~ /^tiny(text|blob)$/i ) {
963 $changed = $size != 255;
964 $size = 255;
965 }
966 elsif ( $type =~ /^(blob|text)$/i ) {
967 $changed = $size != 65_535;
968 $size = 65_535;
969 }
970 elsif ( $type =~ /^medium(blob|text)$/i ) {
971 $changed = $size != 16_777_215;
972 $size = 16_777_215;
973 }
974 elsif ( $type =~ /^long(blob|text)$/i ) {
975 $changed = $size != 4_294_967_295;
976 $size = 4_294_967_295;
977 }
9ab59f87 978 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
979 my %extra = $field->extra;
980 my $longest = 0;
981 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
982 $longest = $len if $len > $longest;
983 }
984 $changed = 1;
985 $size = $longest if $longest;
986 }
987
988
07d6e5f7 989 if ($changed) {
990 # We only want to clone the field, not *everything*
991 { local $field->{table} = undef;
992 $field->parsed_field(dclone($field));
993 $field->parsed_field->{table} = $field->table;
994 }
995 $field->size($size);
996 $field->data_type($type);
9ab59f87 997 $field->sql_data_type( $type_mapping{lc $type} ) if exists $type_mapping{lc $type};
07d6e5f7 998 $field->extra->{list} = $list if @$list;
999 }
1000}
1001
6b2dbb1a 1002
d529894e 10031;
1004
034ecdec 1005# -------------------------------------------------------------------
d529894e 1006# Where man is not nature is barren.
1007# William Blake
034ecdec 1008# -------------------------------------------------------------------
16dc9970 1009
d529894e 1010=pod
16dc9970 1011
1012=head1 AUTHOR
1013
19c5bc53 1014Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 1015Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 1016
1017=head1 SEE ALSO
1018
19c5bc53 1019Parse::RecDescent, SQL::Translator::Schema.
16dc9970 1020
1021=cut