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