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