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