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