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