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