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