Added rules to catch common (but useless) statements.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
dcb4fa06 4# $Id: MySQL.pm,v 1.22 2003-06-04 22:04:53 kycl4rk Exp $
49e1eb70 5# -------------------------------------------------------------------
abfa405a 6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
077ebf34 9#
10# This program is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public License as
12# published by the Free Software Foundation; version 2.
13#
14# This program is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17# General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22# 02111-1307 USA
23# -------------------------------------------------------------------
16dc9970 24
d529894e 25=head1 NAME
26
27SQL::Translator::Parser::MySQL - parser for MySQL
28
29=head1 SYNOPSIS
30
31 use SQL::Translator;
32 use SQL::Translator::Parser::MySQL;
33
34 my $translator = SQL::Translator->new;
35 $translator->parser("SQL::Translator::Parser::MySQL");
36
37=head1 DESCRIPTION
38
39The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
40
629b76f9 41Here's the word from the MySQL site
42(http://www.mysql.com/doc/en/CREATE_TABLE.html):
43
44 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
45 [table_options] [select_statement]
46
47 or
48
49 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;
50
51 create_definition:
52 col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
53 [PRIMARY KEY] [reference_definition]
54 or PRIMARY KEY (index_col_name,...)
55 or KEY [index_name] (index_col_name,...)
56 or INDEX [index_name] (index_col_name,...)
57 or UNIQUE [INDEX] [index_name] (index_col_name,...)
58 or FULLTEXT [INDEX] [index_name] (index_col_name,...)
59 or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
60 [reference_definition]
61 or CHECK (expr)
62
63 type:
64 TINYINT[(length)] [UNSIGNED] [ZEROFILL]
65 or SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
66 or MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
67 or INT[(length)] [UNSIGNED] [ZEROFILL]
68 or INTEGER[(length)] [UNSIGNED] [ZEROFILL]
69 or BIGINT[(length)] [UNSIGNED] [ZEROFILL]
70 or REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
71 or DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
72 or FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
73 or DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
74 or NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
75 or CHAR(length) [BINARY]
76 or VARCHAR(length) [BINARY]
77 or DATE
78 or TIME
79 or TIMESTAMP
80 or DATETIME
81 or TINYBLOB
82 or BLOB
83 or MEDIUMBLOB
84 or LONGBLOB
85 or TINYTEXT
86 or TEXT
87 or MEDIUMTEXT
88 or LONGTEXT
89 or ENUM(value1,value2,value3,...)
90 or SET(value1,value2,value3,...)
91
92 index_col_name:
93 col_name [(length)]
94
95 reference_definition:
96 REFERENCES tbl_name [(index_col_name,...)]
97 [MATCH FULL | MATCH PARTIAL]
98 [ON DELETE reference_option]
99 [ON UPDATE reference_option]
100
101 reference_option:
102 RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
103
104 table_options:
105 TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
106 or AUTO_INCREMENT = #
107 or AVG_ROW_LENGTH = #
108 or CHECKSUM = {0 | 1}
109 or COMMENT = "string"
110 or MAX_ROWS = #
111 or MIN_ROWS = #
112 or PACK_KEYS = {0 | 1 | DEFAULT}
113 or PASSWORD = "string"
114 or DELAY_KEY_WRITE = {0 | 1}
115 or ROW_FORMAT= { default | dynamic | fixed | compressed }
116 or RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=# RAID_CHUNKSIZE=#
117 or UNION = (table_name,[table_name...])
118 or INSERT_METHOD= {NO | FIRST | LAST }
119 or DATA DIRECTORY="absolute path to directory"
120 or INDEX DIRECTORY="absolute path to directory"
121
d529894e 122=cut
123
16dc9970 124use strict;
d529894e 125use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
dcb4fa06 126$VERSION = sprintf "%d.%02d", q$Revision: 1.22 $ =~ /(\d+)\.(\d+)/;
8d0f3086 127$DEBUG = 0 unless defined $DEBUG;
077ebf34 128
d529894e 129use Data::Dumper;
077ebf34 130use Parse::RecDescent;
131use Exporter;
132use base qw(Exporter);
133
134@EXPORT_OK = qw(parse);
135
d529894e 136# Enable warnings within the Parse::RecDescent module.
137$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
138$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
139$::RD_HINT = 1; # Give out hints to help fix problems.
140
d529894e 141$GRAMMAR = q!
142
8ccdeb42 143{
144 our ( %tables, $table_order );
145}
d529894e 146
629b76f9 147#
148# The "eofile" rule makes the parser fail if any "statement" rule
149# fails. Otherwise, the first successful match by a "statement"
150# won't cause the failure needed to know that the parse, as a whole,
151# failed. -ky
152#
153startrule : statement(s) eofile { \%tables }
154
155eofile : /^\Z/
d529894e 156
157statement : comment
dcb4fa06 158 | use
61745327 159 | drop
d529894e 160 | create
161 | <error>
162
dcb4fa06 163use : /use/i WORD ';'
164
61745327 165drop : /drop/i WORD(s) ';'
166
dcb4fa06 167create : CREATE /database/i WORD ';'
168
40c1ade1 169create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
d529894e 170 {
171 my $table_name = $item{'table_name'};
172 $tables{ $table_name }{'order'} = ++$table_order;
173 $tables{ $table_name }{'table_name'} = $table_name;
174
61745327 175 my $i = 1;
40c1ade1 176 for my $definition ( @{ $item[7] } ) {
d529894e 177 if ( $definition->{'type'} eq 'field' ) {
178 my $field_name = $definition->{'name'};
179 $tables{ $table_name }{'fields'}{ $field_name } =
180 { %$definition, order => $i };
181 $i++;
182
183 if ( $definition->{'is_primary_key'} ) {
184 push @{ $tables{ $table_name }{'indices'} },
185 {
186 type => 'primary_key',
187 fields => [ $field_name ],
16dc9970 188 }
d529894e 189 ;
190 }
dd2ef5ae 191 }
40c1ade1 192 elsif ( $definition->{'type'} eq 'foreign_key' ) {
193 for my $field ( @{ $definition->{'fields'} } ) {
194 push @{
195 $tables{$table_name}{'fields'}{$field}{'constraints'}
196 },
197 $definition;
198 }
199 }
d529894e 200 else {
201 push @{ $tables{ $table_name }{'indices'} },
202 $definition;
dd2ef5ae 203 }
d529894e 204 }
dd2ef5ae 205
58a88238 206 for my $opt ( @{ $item{'table_option(s?)'} } ) {
d529894e 207 if ( my ( $key, $val ) = each %$opt ) {
208 $tables{ $table_name }{'table_options'}{ $key } = $val;
dd2ef5ae 209 }
d529894e 210 }
58a88238 211
212 1;
d529894e 213 }
dd2ef5ae 214
40c1ade1 215opt_if_not_exists : /if not exists/i
216
d529894e 217create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
218 {
219 push @{ $tables{ $item{'table_name'} }{'indices'} },
220 {
221 name => $item[4],
222 type => $item[2] ? 'unique' : 'normal',
223 fields => $item[8],
dd2ef5ae 224 }
d529894e 225 ;
226 }
dd2ef5ae 227
d529894e 228create_definition : index
40c1ade1 229 | foreign_key
d529894e 230 | field
231 | <error>
232
233comment : /^\s*(?:#|-{2}).*\n/
234
235blank : /\s*/
236
658637cd 237field : field_name data_type field_qualifier(s?) reference_definition(?)
d529894e 238 {
58a88238 239 my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
d529894e 240 my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
241 delete $qualifiers{'not_null'};
242 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
243 $qualifiers{ $_ } = 1 for @type_quals;
244 }
245
246 $return = {
247 type => 'field',
248 name => $item{'field_name'},
249 data_type => $item{'data_type'}{'type'},
250 size => $item{'data_type'}{'size'},
251 list => $item{'data_type'}{'list'},
252 null => $null,
58a88238 253 constraints => $item{'reference_definition(?)'},
d529894e 254 %qualifiers,
255 }
256 }
257 | <error>
dd2ef5ae 258
d529894e 259field_qualifier : not_null
260 {
261 $return = {
262 null => $item{'not_null'},
263 }
264 }
16dc9970 265
d529894e 266field_qualifier : default_val
267 {
268 $return = {
269 default => $item{'default_val'},
270 }
271 }
16dc9970 272
d529894e 273field_qualifier : auto_inc
274 {
275 $return = {
276 is_auto_inc => $item{'auto_inc'},
277 }
278 }
16dc9970 279
d529894e 280field_qualifier : primary_key
281 {
282 $return = {
283 is_primary_key => $item{'primary_key'},
284 }
285 }
16dc9970 286
d529894e 287field_qualifier : unsigned
288 {
289 $return = {
290 is_unsigned => $item{'unsigned'},
291 }
292 }
16dc9970 293
658637cd 294reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
295 {
40c1ade1 296 $return = {
658637cd 297 type => 'foreign_key',
298 reference_table => $item[2],
299 reference_fields => $item[3][0],
300 match_type => $item[4][0],
301 on_delete_do => $item[5][0],
302 on_update_do => $item[6][0],
303 }
304 }
305
40c1ade1 306
658637cd 307match_type : /match full/i { 'match_full' }
308 |
309 /match partial/i { 'match_partial' }
310
311on_delete_do : /on delete/i reference_option
312 { $item[2] }
313
314on_update_do : /on update/i reference_option
315 { $item[2] }
316
317reference_option: /restrict/i |
318 /cascade/i |
319 /set null/i |
320 /no action/i |
321 /set default/i
322 { $item[1] }
323
d529894e 324index : primary_key_index
325 | unique_index
371f5f88 326 | fulltext_index
d529894e 327 | normal_index
58a88238 328 | <error>
d529894e 329
330table_name : WORD
331
332field_name : WORD
333
334index_name : WORD
335
336data_type : WORD parens_value_list(s?) type_qualifier(s?)
337 {
338 my $type = $item[1];
339 my $size; # field size, applicable only to non-set fields
340 my $list; # set list, applicable only to sets (duh)
341
44fcd0b5 342 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 343 $size = undef;
344 $list = $item[2][0];
345 }
346 else {
347 $size = $item[2][0];
348 $list = [];
349 }
350
6333c482 351 unless ( @{ $size || [] } ) {
352 if ( lc $type eq 'tinyint' ) {
353 $size = [4];
354 }
355 elsif ( lc $type eq 'smallint' ) {
356 $size = [6];
357 }
358 elsif ( lc $type eq 'mediumint' ) {
359 $size = [9];
360 }
c736c39c 361 elsif ( $type =~ /^int(eger)?$/ ) {
6333c482 362 $size = [11];
363 }
364 elsif ( lc $type eq 'bigint' ) {
365 $size = [20];
366 }
c736c39c 367 elsif ( lc $type =~ /(float|double|decimal|numeric|real)/ ) {
6333c482 368 $size = [8,2];
369 }
370 }
371
d529894e 372 $return = {
373 type => $type,
374 size => $size,
375 list => $list,
376 qualifiers => $item[3],
377 }
378 }
16dc9970 379
658637cd 380parens_field_list : '(' field_name(s /,/) ')'
381 { $item[2] }
382
d529894e 383parens_value_list : '(' VALUE(s /,/) ')'
384 { $item[2] }
16dc9970 385
d529894e 386type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
387 { lc $item[1] }
16dc9970 388
d529894e 389field_type : WORD
16dc9970 390
d529894e 391create_index : /create/i /index/i
dd2ef5ae 392
d529894e 393not_null : /not/i /null/i { $return = 0 }
16dc9970 394
d529894e 395unsigned : /unsigned/i { $return = 0 }
16dc9970 396
be019aae 397default_val : /default/i /(?:')?[\w\d:.-]*(?:')?/
d529894e 398 {
399 $item[2] =~ s/'//g;
400 $return = $item[2];
401 }
16dc9970 402
d529894e 403auto_inc : /auto_increment/i { 1 }
16dc9970 404
d529894e 405primary_key : /primary/i /key/i { 1 }
16dc9970 406
40c1ade1 407foreign_key : opt_constraint(?) /foreign key/i WORD(?) parens_field_list reference_definition
408 {
409 $return = {
410 type => 'foreign_key',
411 name => $item[3][0],
412 fields => $item[4],
413 %{ $item{'reference_definition'} },
414 }
415 }
416
417opt_constraint : /constraint/i WORD
418
d529894e 419primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
420 {
421 $return = {
58a88238 422 name => $item{'index_name(?)'}[0],
d529894e 423 type => 'primary_key',
424 fields => $item[4],
58a88238 425 };
d529894e 426 }
16dc9970 427
d529894e 428normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
429 {
430 $return = {
58a88238 431 name => $item{'index_name(?)'}[0],
d529894e 432 type => 'normal',
433 fields => $item[4],
434 }
435 }
16dc9970 436
d529894e 437unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
438 {
439 $return = {
58a88238 440 name => $item{'index_name(?)'}[0],
d529894e 441 type => 'unique',
442 fields => $item[5],
443 }
444 }
16dc9970 445
371f5f88 446fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
447 {
448 $return = {
58a88238 449 name => $item{'index_name(?)'}[0],
371f5f88 450 type => 'fulltext',
451 fields => $item[5],
452 }
453 }
454
d529894e 455name_with_opt_paren : NAME parens_value_list(s?)
456 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 457
371f5f88 458fulltext : /fulltext/i { 1 }
459
d529894e 460unique : /unique/i { 1 }
16dc9970 461
d529894e 462key : /key/i | /index/i
16dc9970 463
44fcd0b5 464table_option : /[^\s;]*/
d529894e 465 {
466 $return = { split /=/, $item[1] }
467 }
16dc9970 468
40c1ade1 469CREATE : /create/i
470
471TEMPORARY : /temporary/i
472
473TABLE : /table/i
474
d529894e 475WORD : /\w+/
16dc9970 476
d529894e 477DIGITS : /\d+/
16dc9970 478
d529894e 479COMMA : ','
16dc9970 480
d529894e 481NAME : "`" /\w+/ "`"
482 { $item[2] }
483 | /\w+/
484 { $item[1] }
16dc9970 485
d529894e 486VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
487 { $item[1] }
488 | /'.*?'/ # XXX doesn't handle embedded quotes
489 { $item[1] }
490 | /NULL/
491 { 'NULL' }
16dc9970 492
d529894e 493!;
16dc9970 494
d529894e 495# -------------------------------------------------------------------
496sub parse {
70944bc5 497 my ( $translator, $data ) = @_;
40c1ade1 498 my $parser = Parse::RecDescent->new($GRAMMAR);
077ebf34 499
e099bee9 500 local $::RD_TRACE = $translator->trace ? 1 : undef;
501 local $DEBUG = $translator->debug;
d529894e 502
503 unless (defined $parser) {
504 return $translator->error("Error instantiating Parse::RecDescent ".
505 "instance: Bad grammer");
506 }
507
508 my $result = $parser->startrule($data);
40c1ade1 509 return $translator->error( "Parse failed." ) unless defined $result;
8ccdeb42 510 warn Dumper( $result ) if $DEBUG;
511
70944bc5 512 my $schema = $translator->schema;
8ccdeb42 513 for my $table_name ( keys %{ $result } ) {
514 my $tdata = $result->{ $table_name };
515 my $table = $schema->add_table(
516 name => $tdata->{'table_name'},
40c1ade1 517 ) or die $schema->error;
8ccdeb42 518
519 my @fields = sort {
520 $tdata->{'fields'}->{$a}->{'order'}
521 <=>
522 $tdata->{'fields'}->{$b}->{'order'}
523 } keys %{ $tdata->{'fields'} };
524
525 for my $fname ( @fields ) {
526 my $fdata = $tdata->{'fields'}{ $fname };
527 my $field = $table->add_field(
528 name => $fdata->{'name'},
529 data_type => $fdata->{'data_type'},
530 size => $fdata->{'size'},
531 default_value => $fdata->{'default'},
532 is_auto_increment => $fdata->{'is_auto_inc'},
533 is_nullable => $fdata->{'null'},
40c1ade1 534 ) or die $table->error;
8ccdeb42 535 }
536 }
537
d529894e 538 return $result;
539}
540
5411;
542
8ccdeb42 543# ----------------------------------------------------
d529894e 544# Where man is not nature is barren.
545# William Blake
8ccdeb42 546# ----------------------------------------------------
16dc9970 547
d529894e 548=pod
16dc9970 549
550=head1 AUTHOR
551
d529894e 552Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 553Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 554
555=head1 SEE ALSO
556
8ccdeb42 557perl(1), Parse::RecDescent, SQL::Translator::Schema.
16dc9970 558
559=cut