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