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