Added default field sizes for numeric fields if not specified, removed
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
6333c482 4# $Id: MySQL.pm,v 1.20 2003-06-03 22:38:18 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 ];
6333c482 126$VERSION = sprintf "%d.%02d", q$Revision: 1.20 $ =~ /(\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
61745327 158 | drop
d529894e 159 | create
160 | <error>
161
61745327 162drop : /drop/i WORD(s) ';'
163
40c1ade1 164create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
d529894e 165 {
166 my $table_name = $item{'table_name'};
167 $tables{ $table_name }{'order'} = ++$table_order;
168 $tables{ $table_name }{'table_name'} = $table_name;
169
61745327 170 my $i = 1;
40c1ade1 171 for my $definition ( @{ $item[7] } ) {
d529894e 172 if ( $definition->{'type'} eq 'field' ) {
173 my $field_name = $definition->{'name'};
174 $tables{ $table_name }{'fields'}{ $field_name } =
175 { %$definition, order => $i };
176 $i++;
177
178 if ( $definition->{'is_primary_key'} ) {
179 push @{ $tables{ $table_name }{'indices'} },
180 {
181 type => 'primary_key',
182 fields => [ $field_name ],
16dc9970 183 }
d529894e 184 ;
185 }
dd2ef5ae 186 }
40c1ade1 187 elsif ( $definition->{'type'} eq 'foreign_key' ) {
188 for my $field ( @{ $definition->{'fields'} } ) {
189 push @{
190 $tables{$table_name}{'fields'}{$field}{'constraints'}
191 },
192 $definition;
193 }
194 }
d529894e 195 else {
196 push @{ $tables{ $table_name }{'indices'} },
197 $definition;
dd2ef5ae 198 }
d529894e 199 }
dd2ef5ae 200
58a88238 201 for my $opt ( @{ $item{'table_option(s?)'} } ) {
d529894e 202 if ( my ( $key, $val ) = each %$opt ) {
203 $tables{ $table_name }{'table_options'}{ $key } = $val;
dd2ef5ae 204 }
d529894e 205 }
58a88238 206
207 1;
d529894e 208 }
dd2ef5ae 209
40c1ade1 210opt_if_not_exists : /if not exists/i
211
d529894e 212create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
213 {
214 push @{ $tables{ $item{'table_name'} }{'indices'} },
215 {
216 name => $item[4],
217 type => $item[2] ? 'unique' : 'normal',
218 fields => $item[8],
dd2ef5ae 219 }
d529894e 220 ;
221 }
dd2ef5ae 222
d529894e 223create_definition : index
40c1ade1 224 | foreign_key
d529894e 225 | field
226 | <error>
227
228comment : /^\s*(?:#|-{2}).*\n/
229
230blank : /\s*/
231
658637cd 232field : field_name data_type field_qualifier(s?) reference_definition(?)
d529894e 233 {
58a88238 234 my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
d529894e 235 my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
236 delete $qualifiers{'not_null'};
237 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
238 $qualifiers{ $_ } = 1 for @type_quals;
239 }
240
241 $return = {
242 type => 'field',
243 name => $item{'field_name'},
244 data_type => $item{'data_type'}{'type'},
245 size => $item{'data_type'}{'size'},
246 list => $item{'data_type'}{'list'},
247 null => $null,
58a88238 248 constraints => $item{'reference_definition(?)'},
d529894e 249 %qualifiers,
250 }
251 }
252 | <error>
dd2ef5ae 253
d529894e 254field_qualifier : not_null
255 {
256 $return = {
257 null => $item{'not_null'},
258 }
259 }
16dc9970 260
d529894e 261field_qualifier : default_val
262 {
263 $return = {
264 default => $item{'default_val'},
265 }
266 }
16dc9970 267
d529894e 268field_qualifier : auto_inc
269 {
270 $return = {
271 is_auto_inc => $item{'auto_inc'},
272 }
273 }
16dc9970 274
d529894e 275field_qualifier : primary_key
276 {
277 $return = {
278 is_primary_key => $item{'primary_key'},
279 }
280 }
16dc9970 281
d529894e 282field_qualifier : unsigned
283 {
284 $return = {
285 is_unsigned => $item{'unsigned'},
286 }
287 }
16dc9970 288
658637cd 289reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
290 {
40c1ade1 291 $return = {
658637cd 292 type => 'foreign_key',
293 reference_table => $item[2],
294 reference_fields => $item[3][0],
295 match_type => $item[4][0],
296 on_delete_do => $item[5][0],
297 on_update_do => $item[6][0],
298 }
299 }
300
40c1ade1 301
658637cd 302match_type : /match full/i { 'match_full' }
303 |
304 /match partial/i { 'match_partial' }
305
306on_delete_do : /on delete/i reference_option
307 { $item[2] }
308
309on_update_do : /on update/i reference_option
310 { $item[2] }
311
312reference_option: /restrict/i |
313 /cascade/i |
314 /set null/i |
315 /no action/i |
316 /set default/i
317 { $item[1] }
318
d529894e 319index : primary_key_index
320 | unique_index
371f5f88 321 | fulltext_index
d529894e 322 | normal_index
58a88238 323 | <error>
d529894e 324
325table_name : WORD
326
327field_name : WORD
328
329index_name : WORD
330
331data_type : WORD parens_value_list(s?) type_qualifier(s?)
332 {
333 my $type = $item[1];
334 my $size; # field size, applicable only to non-set fields
335 my $list; # set list, applicable only to sets (duh)
336
44fcd0b5 337 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 338 $size = undef;
339 $list = $item[2][0];
340 }
341 else {
342 $size = $item[2][0];
343 $list = [];
344 }
345
6333c482 346 unless ( @{ $size || [] } ) {
347 if ( lc $type eq 'tinyint' ) {
348 $size = [4];
349 }
350 elsif ( lc $type eq 'smallint' ) {
351 $size = [6];
352 }
353 elsif ( lc $type eq 'mediumint' ) {
354 $size = [9];
355 }
356 elsif ( lc $type eq 'int' ) {
357 $size = [11];
358 }
359 elsif ( lc $type eq 'bigint' ) {
360 $size = [20];
361 }
362 elsif ( lc $type eq 'float' ) {
363 $size = [8,2];
364 }
365 elsif ( lc $type eq 'double' ) {
366 $size = [8,2];
367 }
368 elsif ( lc $type eq 'decimal' ) {
369 $size = [8,2];
370 }
371 }
372
d529894e 373 $return = {
374 type => $type,
375 size => $size,
376 list => $list,
377 qualifiers => $item[3],
378 }
379 }
16dc9970 380
658637cd 381parens_field_list : '(' field_name(s /,/) ')'
382 { $item[2] }
383
d529894e 384parens_value_list : '(' VALUE(s /,/) ')'
385 { $item[2] }
16dc9970 386
d529894e 387type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
388 { lc $item[1] }
16dc9970 389
d529894e 390field_type : WORD
16dc9970 391
d529894e 392create_index : /create/i /index/i
dd2ef5ae 393
d529894e 394not_null : /not/i /null/i { $return = 0 }
16dc9970 395
d529894e 396unsigned : /unsigned/i { $return = 0 }
16dc9970 397
be019aae 398default_val : /default/i /(?:')?[\w\d:.-]*(?:')?/
d529894e 399 {
400 $item[2] =~ s/'//g;
401 $return = $item[2];
402 }
16dc9970 403
d529894e 404auto_inc : /auto_increment/i { 1 }
16dc9970 405
d529894e 406primary_key : /primary/i /key/i { 1 }
16dc9970 407
40c1ade1 408foreign_key : opt_constraint(?) /foreign key/i WORD(?) parens_field_list reference_definition
409 {
410 $return = {
411 type => 'foreign_key',
412 name => $item[3][0],
413 fields => $item[4],
414 %{ $item{'reference_definition'} },
415 }
416 }
417
418opt_constraint : /constraint/i WORD
419
d529894e 420primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
421 {
422 $return = {
58a88238 423 name => $item{'index_name(?)'}[0],
d529894e 424 type => 'primary_key',
425 fields => $item[4],
58a88238 426 };
d529894e 427 }
16dc9970 428
d529894e 429normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
430 {
431 $return = {
58a88238 432 name => $item{'index_name(?)'}[0],
d529894e 433 type => 'normal',
434 fields => $item[4],
435 }
436 }
16dc9970 437
d529894e 438unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
439 {
440 $return = {
58a88238 441 name => $item{'index_name(?)'}[0],
d529894e 442 type => 'unique',
443 fields => $item[5],
444 }
445 }
16dc9970 446
371f5f88 447fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
448 {
449 $return = {
58a88238 450 name => $item{'index_name(?)'}[0],
371f5f88 451 type => 'fulltext',
452 fields => $item[5],
453 }
454 }
455
d529894e 456name_with_opt_paren : NAME parens_value_list(s?)
457 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 458
371f5f88 459fulltext : /fulltext/i { 1 }
460
d529894e 461unique : /unique/i { 1 }
16dc9970 462
d529894e 463key : /key/i | /index/i
16dc9970 464
44fcd0b5 465table_option : /[^\s;]*/
d529894e 466 {
467 $return = { split /=/, $item[1] }
468 }
16dc9970 469
40c1ade1 470CREATE : /create/i
471
472TEMPORARY : /temporary/i
473
474TABLE : /table/i
475
d529894e 476WORD : /\w+/
16dc9970 477
d529894e 478DIGITS : /\d+/
16dc9970 479
d529894e 480COMMA : ','
16dc9970 481
d529894e 482NAME : "`" /\w+/ "`"
483 { $item[2] }
484 | /\w+/
485 { $item[1] }
16dc9970 486
d529894e 487VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
488 { $item[1] }
489 | /'.*?'/ # XXX doesn't handle embedded quotes
490 { $item[1] }
491 | /NULL/
492 { 'NULL' }
16dc9970 493
d529894e 494!;
16dc9970 495
d529894e 496# -------------------------------------------------------------------
497sub parse {
70944bc5 498 my ( $translator, $data ) = @_;
40c1ade1 499 my $parser = Parse::RecDescent->new($GRAMMAR);
077ebf34 500
e099bee9 501 local $::RD_TRACE = $translator->trace ? 1 : undef;
502 local $DEBUG = $translator->debug;
d529894e 503
504 unless (defined $parser) {
505 return $translator->error("Error instantiating Parse::RecDescent ".
506 "instance: Bad grammer");
507 }
508
509 my $result = $parser->startrule($data);
40c1ade1 510 return $translator->error( "Parse failed." ) unless defined $result;
8ccdeb42 511 warn Dumper( $result ) if $DEBUG;
512
70944bc5 513 my $schema = $translator->schema;
8ccdeb42 514 for my $table_name ( keys %{ $result } ) {
515 my $tdata = $result->{ $table_name };
516 my $table = $schema->add_table(
517 name => $tdata->{'table_name'},
40c1ade1 518 ) or die $schema->error;
8ccdeb42 519
520 my @fields = sort {
521 $tdata->{'fields'}->{$a}->{'order'}
522 <=>
523 $tdata->{'fields'}->{$b}->{'order'}
524 } keys %{ $tdata->{'fields'} };
525
526 for my $fname ( @fields ) {
527 my $fdata = $tdata->{'fields'}{ $fname };
528 my $field = $table->add_field(
529 name => $fdata->{'name'},
530 data_type => $fdata->{'data_type'},
531 size => $fdata->{'size'},
532 default_value => $fdata->{'default'},
533 is_auto_increment => $fdata->{'is_auto_inc'},
534 is_nullable => $fdata->{'null'},
40c1ade1 535 ) or die $table->error;
8ccdeb42 536 }
537 }
538
d529894e 539 return $result;
540}
541
5421;
543
8ccdeb42 544# ----------------------------------------------------
d529894e 545# Where man is not nature is barren.
546# William Blake
8ccdeb42 547# ----------------------------------------------------
16dc9970 548
d529894e 549=pod
16dc9970 550
551=head1 AUTHOR
552
d529894e 553Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
8ccdeb42 554Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
16dc9970 555
556=head1 SEE ALSO
557
8ccdeb42 558perl(1), Parse::RecDescent, SQL::Translator::Schema.
16dc9970 559
560=cut