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