Patch from rbo to support multiple database events per trigger
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
d31c185b 4# $Id: MySQL.pm,v 1.58 2007-03-19 17:15:24 duality72 Exp $
49e1eb70 5# -------------------------------------------------------------------
90075866 6# Copyright (C) 2002-4 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;
d529894e 151use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
d31c185b 152$VERSION = sprintf "%d.%02d", q$Revision: 1.58 $ =~ /(\d+)\.(\d+)/;
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 {
600 $return = $item[2];
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 }
19c5bc53 716 | WORD /\s*=\s*/ WORD
717 {
718 $return = { $item[1] => $item[3] };
719 }
bd30a9a2 720
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
531652d6 741NAME : BACKTICK /[^`]+/ BACKTICK
d529894e 742 { $item[2] }
743 | /\w+/
744 { $item[1] }
16dc9970 745
d529894e 746VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
747 { $item[1] }
f2cf1734 748 | /'.*?'/
749 {
750 # remove leading/trailing quotes
751 my $val = $item[1];
752 $val =~ s/^['"]|['"]$//g;
753 $return = $val;
754 }
d529894e 755 | /NULL/
756 { 'NULL' }
16dc9970 757
bd30a9a2 758CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
07d6e5f7 759 | /now\(\)/i
760 { 'CURRENT_TIMESTAMP' }
761
9bf756df 762END_OF_GRAMMAR
16dc9970 763
d529894e 764# -------------------------------------------------------------------
765sub parse {
70944bc5 766 my ( $translator, $data ) = @_;
40c1ade1 767 my $parser = Parse::RecDescent->new($GRAMMAR);
077ebf34 768
e099bee9 769 local $::RD_TRACE = $translator->trace ? 1 : undef;
770 local $DEBUG = $translator->debug;
d529894e 771
772 unless (defined $parser) {
773 return $translator->error("Error instantiating Parse::RecDescent ".
774 "instance: Bad grammer");
775 }
d31c185b 776
777 # Preprocess for MySQL-specific and not-before-version comments from mysqldump
5d666b31 778 my $parser_version =
779 parse_mysql_version ($translator->parser_args->{mysql_parser_version}, 'mysql')
780 || DEFAULT_PARSER_VERSION;
d31c185b 781 while ( $data =~ s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es ) {}
d529894e 782
783 my $result = $parser->startrule($data);
40c1ade1 784 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 785 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 786
70944bc5 787 my $schema = $translator->schema;
13aec984 788 $schema->name($result->{'database_name'}) if $result->{'database_name'};
789
034ecdec 790 my @tables = sort {
13aec984 791 $result->{'tables'}{ $a }{'order'}
792 <=>
793 $result->{'tables'}{ $b }{'order'}
794 } keys %{ $result->{'tables'} };
034ecdec 795
796 for my $table_name ( @tables ) {
13aec984 797 my $tdata = $result->{tables}{ $table_name };
8ccdeb42 798 my $table = $schema->add_table(
799 name => $tdata->{'table_name'},
40c1ade1 800 ) or die $schema->error;
8ccdeb42 801
734dfc91 802 $table->comments( $tdata->{'comments'} );
f2cf1734 803
8ccdeb42 804 my @fields = sort {
805 $tdata->{'fields'}->{$a}->{'order'}
806 <=>
807 $tdata->{'fields'}->{$b}->{'order'}
808 } keys %{ $tdata->{'fields'} };
809
810 for my $fname ( @fields ) {
811 my $fdata = $tdata->{'fields'}{ $fname };
812 my $field = $table->add_field(
813 name => $fdata->{'name'},
814 data_type => $fdata->{'data_type'},
815 size => $fdata->{'size'},
816 default_value => $fdata->{'default'},
817 is_auto_increment => $fdata->{'is_auto_inc'},
818 is_nullable => $fdata->{'null'},
88b89793 819 comments => $fdata->{'comments'},
40c1ade1 820 ) or die $table->error;
f2cf1734 821
822 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
823
bd30a9a2 824 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 825 'character set', 'on update' ) {
f2cf1734 826 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
827 next if ref $val eq 'ARRAY' && !@$val;
828 $field->extra( $qual, $val );
829 }
830 }
831
bd356af8 832 if ( $fdata->{'has_index'} ) {
833 $table->add_index(
834 name => '',
835 type => 'NORMAL',
836 fields => $fdata->{'name'},
837 ) or die $table->error;
838 }
839
840 if ( $fdata->{'is_unique'} ) {
841 $table->add_constraint(
842 name => '',
843 type => 'UNIQUE',
844 fields => $fdata->{'name'},
845 ) or die $table->error;
846 }
847
f2cf1734 848 for my $cdata ( @{ $fdata->{'constraints'} } ) {
849 next unless $cdata->{'type'} eq 'foreign_key';
850 $cdata->{'fields'} ||= [ $field->name ];
851 push @{ $tdata->{'constraints'} }, $cdata;
852 }
07d6e5f7 853
f2cf1734 854 }
855
856 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
857 my $index = $table->add_index(
858 name => $idata->{'name'},
859 type => uc $idata->{'type'},
860 fields => $idata->{'fields'},
861 ) or die $table->error;
862 }
863
02a21f1a 864 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
865 $table->options( \@options ) or die $table->error;
866 }
867
f2cf1734 868 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
869 my $constraint = $table->add_constraint(
870 name => $cdata->{'name'},
871 type => $cdata->{'type'},
872 fields => $cdata->{'fields'},
873 reference_table => $cdata->{'reference_table'},
874 reference_fields => $cdata->{'reference_fields'},
875 match_type => $cdata->{'match_type'} || '',
100684f3 876 on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
877 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
f2cf1734 878 ) or die $table->error;
8ccdeb42 879 }
07d6e5f7 880
881 # After the constrains and PK/idxs have been created, we normalize fields
882 normalize_field($_) for $table->get_fields;
8ccdeb42 883 }
d31c185b 884
885 my @procedures = sort {
886 $result->{procedures}->{ $a }->{'order'} <=> $result->{procedures}->{ $b }->{'order'}
887 } keys %{ $result->{procedures} };
888 foreach my $proc_name (@procedures) {
07d6e5f7 889 $schema->add_procedure(
890 name => $proc_name,
891 owner => $result->{procedures}->{$proc_name}->{owner},
892 sql => $result->{procedures}->{$proc_name}->{sql},
893 );
d31c185b 894 }
895
896 my @views = sort {
897 $result->{views}->{ $a }->{'order'} <=> $result->{views}->{ $b }->{'order'}
898 } keys %{ $result->{views} };
899 foreach my $view_name (keys %{ $result->{views} }) {
07d6e5f7 900 $schema->add_view(
901 name => $view_name,
902 sql => $result->{views}->{$view_name}->{sql},
903 );
d31c185b 904 }
8ccdeb42 905
f62bd16c 906 return 1;
d529894e 907}
908
07d6e5f7 909# Takes a field, and returns
910sub normalize_field {
911 my ($field) = @_;
912 my ($size, $type, $list, $changed) = @_;
913
914 $size = $field->size;
915 $type = $field->data_type;
916 $list = $field->extra->{list} || [];
917
918 if ( !ref $size && $size eq 0 ) {
919 if ( lc $type eq 'tinyint' ) {
920 $changed = $size != 4;
921 $size = 4;
922 }
923 elsif ( lc $type eq 'smallint' ) {
924 $changed = $size != 6;
925 $size = 6;
926 }
927 elsif ( lc $type eq 'mediumint' ) {
928 $changed = $size != 9;
929 $size = 9;
930 }
931 elsif ( $type =~ /^int(eger)?$/i ) {
932 $changed = $size != 11 || $type ne 'int';
933 $type = 'int';
934 $size = 11;
935 }
936 elsif ( lc $type eq 'bigint' ) {
937 $changed = $size != 20;
938 $size = 20;
939 }
940 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
941 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
54b6e490 942 $changed = @$old_size != 2 || $old_size->[0] != 8 || $old_size->[1] != 2;
07d6e5f7 943 $size = [8,2];
944 }
945 }
946
947 if ( $type =~ /^tiny(text|blob)$/i ) {
948 $changed = $size != 255;
949 $size = 255;
950 }
951 elsif ( $type =~ /^(blob|text)$/i ) {
952 $changed = $size != 65_535;
953 $size = 65_535;
954 }
955 elsif ( $type =~ /^medium(blob|text)$/i ) {
956 $changed = $size != 16_777_215;
957 $size = 16_777_215;
958 }
959 elsif ( $type =~ /^long(blob|text)$/i ) {
960 $changed = $size != 4_294_967_295;
961 $size = 4_294_967_295;
962 }
9ab59f87 963 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
964 my %extra = $field->extra;
965 my $longest = 0;
966 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
967 $longest = $len if $len > $longest;
968 }
969 $changed = 1;
970 $size = $longest if $longest;
971 }
972
973
07d6e5f7 974 if ($changed) {
975 # We only want to clone the field, not *everything*
976 { local $field->{table} = undef;
977 $field->parsed_field(dclone($field));
978 $field->parsed_field->{table} = $field->table;
979 }
980 $field->size($size);
981 $field->data_type($type);
9ab59f87 982 $field->sql_data_type( $type_mapping{lc $type} ) if exists $type_mapping{lc $type};
07d6e5f7 983 $field->extra->{list} = $list if @$list;
984 }
985}
986
6b2dbb1a 987
d529894e 9881;
989
034ecdec 990# -------------------------------------------------------------------
d529894e 991# Where man is not nature is barren.
992# William Blake
034ecdec 993# -------------------------------------------------------------------
16dc9970 994
d529894e 995=pod
16dc9970 996
997=head1 AUTHOR
998
19c5bc53 999Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 1000Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 1001
1002=head1 SEE ALSO
1003
19c5bc53 1004Parse::RecDescent, SQL::Translator::Schema.
16dc9970 1005
1006=cut