Missed file from default-value-improvements commit
[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
58a88238 537 | <error>
d529894e 538
0d41bc9b 539table_name : NAME
d529894e 540
0d41bc9b 541field_name : NAME
d529894e 542
02a21f1a 543index_name : NAME
d529894e 544
545data_type : WORD parens_value_list(s?) type_qualifier(s?)
546 {
547 my $type = $item[1];
548 my $size; # field size, applicable only to non-set fields
549 my $list; # set list, applicable only to sets (duh)
550
44fcd0b5 551 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 552 $size = undef;
553 $list = $item[2][0];
554 }
555 else {
556 $size = $item[2][0];
557 $list = [];
558 }
559
256d534a 560
d529894e 561 $return = {
562 type => $type,
563 size => $size,
564 list => $list,
565 qualifiers => $item[3],
566 }
567 }
16dc9970 568
658637cd 569parens_field_list : '(' field_name(s /,/) ')'
570 { $item[2] }
571
d529894e 572parens_value_list : '(' VALUE(s /,/) ')'
573 { $item[2] }
16dc9970 574
d529894e 575type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
576 { lc $item[1] }
16dc9970 577
d529894e 578field_type : WORD
16dc9970 579
d529894e 580create_index : /create/i /index/i
dd2ef5ae 581
e78d62f2 582not_null : /not/i /null/i
583 { $return = 0 }
584 |
585 /null/i
586 { $return = 1 }
16dc9970 587
d529894e 588unsigned : /unsigned/i { $return = 0 }
16dc9970 589
09fa21a6 590#default_val : /default/i /(?:')?[\s\w\d:.-]*(?:')?/
591# {
592# $item[2] =~ s/'//g;
593# $return = $item[2];
594# }
595
6fa97af6 596default_val :
597 /default/i 'CURRENT_TIMESTAMP'
598 {
599 $return = $item[2];
600 }
601 |
602 /default/i /'(?:.*?\\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
09fa21a6 603 {
604 $item[2] =~ s/^\s*'|'\s*$//g;
d529894e 605 $return = $item[2];
606 }
16dc9970 607
d529894e 608auto_inc : /auto_increment/i { 1 }
16dc9970 609
d529894e 610primary_key : /primary/i /key/i { 1 }
16dc9970 611
f2cf1734 612constraint : primary_key_def
613 | unique_key_def
614 | foreign_key_def
615 | <error>
616
02a21f1a 617foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
40c1ade1 618 {
619 $return = {
f2cf1734 620 supertype => 'constraint',
40c1ade1 621 type => 'foreign_key',
02a21f1a 622 name => $item[1],
09fa21a6 623 fields => $item[2],
40c1ade1 624 %{ $item{'reference_definition'} },
625 }
626 }
627
e78d62f2 628foreign_key_def_begin : /constraint/i /foreign key/i WORD
629 { $return = $item[3] }
630 |
631 /constraint/i NAME /foreign key/i
632 { $return = $item[2] }
633 |
634 /constraint/i /foreign key/i
02a21f1a 635 { $return = '' }
636 |
e78d62f2 637 /foreign key/i WORD
02a21f1a 638 { $return = $item[2] }
639 |
640 /foreign key/i
641 { $return = '' }
40c1ade1 642
1853ba82 643primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 644 {
f2cf1734 645 $return = {
646 supertype => 'constraint',
647 name => $item{'index_name(?)'}[0],
648 type => 'primary_key',
649 fields => $item[4],
58a88238 650 };
d529894e 651 }
16dc9970 652
f2cf1734 653unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 654 {
f2cf1734 655 $return = {
656 supertype => 'constraint',
657 name => $item{'index_name(?)'}[0],
658 type => 'unique',
659 fields => $item[5],
d529894e 660 }
661 }
16dc9970 662
f2cf1734 663normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
d529894e 664 {
f2cf1734 665 $return = {
666 supertype => 'index',
667 type => 'normal',
668 name => $item{'index_name(?)'}[0],
669 fields => $item[4],
d529894e 670 }
671 }
16dc9970 672
f2cf1734 673fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
371f5f88 674 {
f2cf1734 675 $return = {
676 supertype => 'index',
677 type => 'fulltext',
678 name => $item{'index_name(?)'}[0],
679 fields => $item[5],
371f5f88 680 }
681 }
682
d529894e 683name_with_opt_paren : NAME parens_value_list(s?)
684 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 685
041e659f 686UNIQUE : /unique/i
16dc9970 687
f2cf1734 688KEY : /key/i | /index/i
16dc9970 689
19c5bc53 690table_option : /comment/i /=/ /'.*?'/
35843e6b 691 {
692 my $comment = $item[3];
693 $comment =~ s/^'//;
694 $comment =~ s/'$//;
695 $return = { comment => $comment };
696 }
bb4c66d1 697 | /(default )?(charset|character set)/i /\s*=?\s*/ WORD
d529894e 698 {
bd30a9a2 699 $return = { 'CHARACTER SET' => $item[3] };
d529894e 700 }
a7f49dfb 701 | /collate/i WORD
702 {
703 $return = { 'COLLATE' => $item[2] }
704 }
19c5bc53 705 | WORD /\s*=\s*/ WORD
706 {
707 $return = { $item[1] => $item[3] };
708 }
bd30a9a2 709
710default : /default/i
16dc9970 711
13aec984 712ADD : /add/i
713
714ALTER : /alter/i
715
40c1ade1 716CREATE : /create/i
717
718TEMPORARY : /temporary/i
719
720TABLE : /table/i
721
d529894e 722WORD : /\w+/
16dc9970 723
d529894e 724DIGITS : /\d+/
16dc9970 725
d529894e 726COMMA : ','
16dc9970 727
a7f49dfb 728BACKTICK : '`'
729
730NAME : BACKTICK /\w+/ BACKTICK
d529894e 731 { $item[2] }
732 | /\w+/
733 { $item[1] }
16dc9970 734
d529894e 735VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
736 { $item[1] }
f2cf1734 737 | /'.*?'/
738 {
739 # remove leading/trailing quotes
740 my $val = $item[1];
741 $val =~ s/^['"]|['"]$//g;
742 $return = $val;
743 }
d529894e 744 | /NULL/
745 { 'NULL' }
16dc9970 746
bd30a9a2 747CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
07d6e5f7 748 | /now\(\)/i
749 { 'CURRENT_TIMESTAMP' }
750
9bf756df 751END_OF_GRAMMAR
16dc9970 752
d529894e 753# -------------------------------------------------------------------
754sub parse {
70944bc5 755 my ( $translator, $data ) = @_;
40c1ade1 756 my $parser = Parse::RecDescent->new($GRAMMAR);
077ebf34 757
e099bee9 758 local $::RD_TRACE = $translator->trace ? 1 : undef;
759 local $DEBUG = $translator->debug;
d529894e 760
761 unless (defined $parser) {
762 return $translator->error("Error instantiating Parse::RecDescent ".
763 "instance: Bad grammer");
764 }
d31c185b 765
766 # Preprocess for MySQL-specific and not-before-version comments from mysqldump
5d666b31 767 my $parser_version =
768 parse_mysql_version ($translator->parser_args->{mysql_parser_version}, 'mysql')
769 || DEFAULT_PARSER_VERSION;
d31c185b 770 while ( $data =~ s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es ) {}
d529894e 771
772 my $result = $parser->startrule($data);
40c1ade1 773 return $translator->error( "Parse failed." ) unless defined $result;
13aec984 774 warn "Parse result:".Dumper( $result ) if $DEBUG;
8ccdeb42 775
70944bc5 776 my $schema = $translator->schema;
13aec984 777 $schema->name($result->{'database_name'}) if $result->{'database_name'};
778
034ecdec 779 my @tables = sort {
13aec984 780 $result->{'tables'}{ $a }{'order'}
781 <=>
782 $result->{'tables'}{ $b }{'order'}
783 } keys %{ $result->{'tables'} };
034ecdec 784
785 for my $table_name ( @tables ) {
13aec984 786 my $tdata = $result->{tables}{ $table_name };
8ccdeb42 787 my $table = $schema->add_table(
788 name => $tdata->{'table_name'},
40c1ade1 789 ) or die $schema->error;
8ccdeb42 790
734dfc91 791 $table->comments( $tdata->{'comments'} );
f2cf1734 792
8ccdeb42 793 my @fields = sort {
794 $tdata->{'fields'}->{$a}->{'order'}
795 <=>
796 $tdata->{'fields'}->{$b}->{'order'}
797 } keys %{ $tdata->{'fields'} };
798
799 for my $fname ( @fields ) {
800 my $fdata = $tdata->{'fields'}{ $fname };
801 my $field = $table->add_field(
802 name => $fdata->{'name'},
803 data_type => $fdata->{'data_type'},
804 size => $fdata->{'size'},
805 default_value => $fdata->{'default'},
806 is_auto_increment => $fdata->{'is_auto_inc'},
807 is_nullable => $fdata->{'null'},
88b89793 808 comments => $fdata->{'comments'},
40c1ade1 809 ) or die $table->error;
f2cf1734 810
811 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
812
bd30a9a2 813 for my $qual ( qw[ binary unsigned zerofill list collate ],
07d6e5f7 814 'character set', 'on update' ) {
f2cf1734 815 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
816 next if ref $val eq 'ARRAY' && !@$val;
817 $field->extra( $qual, $val );
818 }
819 }
820
bd356af8 821 if ( $fdata->{'has_index'} ) {
822 $table->add_index(
823 name => '',
824 type => 'NORMAL',
825 fields => $fdata->{'name'},
826 ) or die $table->error;
827 }
828
829 if ( $fdata->{'is_unique'} ) {
830 $table->add_constraint(
831 name => '',
832 type => 'UNIQUE',
833 fields => $fdata->{'name'},
834 ) or die $table->error;
835 }
836
f2cf1734 837 for my $cdata ( @{ $fdata->{'constraints'} } ) {
838 next unless $cdata->{'type'} eq 'foreign_key';
839 $cdata->{'fields'} ||= [ $field->name ];
840 push @{ $tdata->{'constraints'} }, $cdata;
841 }
07d6e5f7 842
f2cf1734 843 }
844
845 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
846 my $index = $table->add_index(
847 name => $idata->{'name'},
848 type => uc $idata->{'type'},
849 fields => $idata->{'fields'},
850 ) or die $table->error;
851 }
852
02a21f1a 853 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
854 $table->options( \@options ) or die $table->error;
855 }
856
f2cf1734 857 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
858 my $constraint = $table->add_constraint(
859 name => $cdata->{'name'},
860 type => $cdata->{'type'},
861 fields => $cdata->{'fields'},
862 reference_table => $cdata->{'reference_table'},
863 reference_fields => $cdata->{'reference_fields'},
864 match_type => $cdata->{'match_type'} || '',
100684f3 865 on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
866 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
f2cf1734 867 ) or die $table->error;
8ccdeb42 868 }
07d6e5f7 869
870 # After the constrains and PK/idxs have been created, we normalize fields
871 normalize_field($_) for $table->get_fields;
8ccdeb42 872 }
d31c185b 873
874 my @procedures = sort {
875 $result->{procedures}->{ $a }->{'order'} <=> $result->{procedures}->{ $b }->{'order'}
876 } keys %{ $result->{procedures} };
877 foreach my $proc_name (@procedures) {
07d6e5f7 878 $schema->add_procedure(
879 name => $proc_name,
880 owner => $result->{procedures}->{$proc_name}->{owner},
881 sql => $result->{procedures}->{$proc_name}->{sql},
882 );
d31c185b 883 }
884
885 my @views = sort {
886 $result->{views}->{ $a }->{'order'} <=> $result->{views}->{ $b }->{'order'}
887 } keys %{ $result->{views} };
888 foreach my $view_name (keys %{ $result->{views} }) {
07d6e5f7 889 $schema->add_view(
890 name => $view_name,
891 sql => $result->{views}->{$view_name}->{sql},
892 );
d31c185b 893 }
8ccdeb42 894
f62bd16c 895 return 1;
d529894e 896}
897
07d6e5f7 898# Takes a field, and returns
899sub normalize_field {
900 my ($field) = @_;
901 my ($size, $type, $list, $changed) = @_;
902
903 $size = $field->size;
904 $type = $field->data_type;
905 $list = $field->extra->{list} || [];
906
907 if ( !ref $size && $size eq 0 ) {
908 if ( lc $type eq 'tinyint' ) {
909 $changed = $size != 4;
910 $size = 4;
911 }
912 elsif ( lc $type eq 'smallint' ) {
913 $changed = $size != 6;
914 $size = 6;
915 }
916 elsif ( lc $type eq 'mediumint' ) {
917 $changed = $size != 9;
918 $size = 9;
919 }
920 elsif ( $type =~ /^int(eger)?$/i ) {
921 $changed = $size != 11 || $type ne 'int';
922 $type = 'int';
923 $size = 11;
924 }
925 elsif ( lc $type eq 'bigint' ) {
926 $changed = $size != 20;
927 $size = 20;
928 }
929 elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
930 my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
54b6e490 931 $changed = @$old_size != 2 || $old_size->[0] != 8 || $old_size->[1] != 2;
07d6e5f7 932 $size = [8,2];
933 }
934 }
935
936 if ( $type =~ /^tiny(text|blob)$/i ) {
937 $changed = $size != 255;
938 $size = 255;
939 }
940 elsif ( $type =~ /^(blob|text)$/i ) {
941 $changed = $size != 65_535;
942 $size = 65_535;
943 }
944 elsif ( $type =~ /^medium(blob|text)$/i ) {
945 $changed = $size != 16_777_215;
946 $size = 16_777_215;
947 }
948 elsif ( $type =~ /^long(blob|text)$/i ) {
949 $changed = $size != 4_294_967_295;
950 $size = 4_294_967_295;
951 }
9ab59f87 952 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
953 my %extra = $field->extra;
954 my $longest = 0;
955 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
956 $longest = $len if $len > $longest;
957 }
958 $changed = 1;
959 $size = $longest if $longest;
960 }
961
962
07d6e5f7 963 if ($changed) {
964 # We only want to clone the field, not *everything*
965 { local $field->{table} = undef;
966 $field->parsed_field(dclone($field));
967 $field->parsed_field->{table} = $field->table;
968 }
969 $field->size($size);
970 $field->data_type($type);
9ab59f87 971 $field->sql_data_type( $type_mapping{lc $type} ) if exists $type_mapping{lc $type};
07d6e5f7 972 $field->extra->{list} = $list if @$list;
973 }
974}
975
6b2dbb1a 976
d529894e 9771;
978
034ecdec 979# -------------------------------------------------------------------
d529894e 980# Where man is not nature is barren.
981# William Blake
034ecdec 982# -------------------------------------------------------------------
16dc9970 983
d529894e 984=pod
16dc9970 985
986=head1 AUTHOR
987
19c5bc53 988Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 989Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 990
991=head1 SEE ALSO
992
19c5bc53 993Parse::RecDescent, SQL::Translator::Schema.
16dc9970 994
995=cut